﻿WEBVTT

00:00:00.000 --> 00:00:06.000
Translated by visionNoob, KNU
https://github.com/insurgent92/CS231N_17_KOR_SUB

00:00:09.679 --> 00:00:13.891
12시가 지났으니
진행하도록 하겠습니다.

00:00:13.891 --> 00:00:17.822
이번 8강에서는
Deep learning software에 대해 알아볼 것입니다.

00:00:17.822 --> 00:00:21.283
매년 아주 많은 변화가 생기는
정말 재미있는 주제입니다.

00:00:21.283 --> 00:00:25.621
다만 매년 너무 많이 바뀌어서
매번 강의준비가 좀 빡세긴 합니다.

00:00:25.621 --> 00:00:30.024
우선 몇가지 공지사항을 전달하겠습니다.

00:00:30.024 --> 00:00:34.563
우선 지난 화요일에 프로젝트 기획서
제출기한이 마감되었습니다.

00:00:34.563 --> 00:00:42.766
다들 좋은 아이디어를 가지고 기한내에 잘 제출했길 바랍니다.

00:00:42.766 --> 00:00:50.217
현재 프로젝트 분야에 맞는 적절한 TA를 배치중에 있습니다.

00:00:50.217 --> 00:00:54.264
추후에 더 많은 정보가 공지될 것입니다.

00:00:54.264 --> 00:00:56.563
그리고 현재 과제1을 채점중에 있습니다.

00:00:56.563 --> 00:01:00.942
가능한 빨리 점수를 알려드리도록 하겠습니다.

00:01:00.942 --> 00:01:08.680
그리고 과제2의 제출기한도 얼마 남지 않았습니다.
다음 주 까지 라는 것을 꼭 명심하시기 바랍니다.

00:01:08.680 --> 00:01:16.231
과제2를 진행할 때 반드시 Google Cloud를 잘 종료시켜서
각자 크래딧을 아끼도록 하세요

00:01:16.231 --> 00:01:24.812
그리고 다시한번 말씀드리고 싶은 것은 과제2에서 GPU를
사용한만한 것은 마지막 문제뿐이 없다는 것입니다.

00:01:24.812 --> 00:01:32.250
나머지 문제들은 python과 numpy만 사용하기 때문에
GPU를 쓸 일이 없습니다.

00:01:32.250 --> 00:01:36.701
GPU는 정말 필요할 때만 사용하셔서 크래딧을 아껴 쓰시기 바랍니다.

00:01:36.701 --> 00:01:39.973
마지막으로 이제 곧 중간고사가 예정되어 있습니다.

00:01:39.973 --> 00:01:45.683
벌써 중간고사 기간이라는 것이 믿어지지 않지만
중간고사는 5월 9일 화요일 수업시간에 진행됩니다.

00:01:45.683 --> 00:01:47.901
중간고사는 이론중심으로 출제될 것입니다.

00:01:47.901 --> 00:01:57.071
지금까지 수업을 잘 이해하고 있는지를 확인하기 위한
몇가지 이론적인 문제들을 출제할 것입니다.

00:01:57.071 --> 00:02:02.506
몇 가지 예상문제를 올려놓도록 하겠습니다.

00:02:02.506 --> 00:02:03.695
질문 있나요?

00:02:03.695 --> 00:02:05.310
[학생이 질문]

00:02:05.310 --> 00:02:10.675
오픈북이냐고 질문하셨는데요
오픈북은 아닙니다.

00:02:10.675 --> 00:02:15.671
지금까지 항상 클로즈북으로 진행해 왔습니다.

00:02:15.671 --> 00:02:21.735
여러분이 수업에서 다룬 것들의 직관들을 얼마나
이해했는지를 확인하는 것 위주로 출제될 것입니다.

00:02:23.618 --> 00:02:27.577
자 그럼 지난시간의 내용을 간단하게 복습해 보도록 하겠습니다.

00:02:27.577 --> 00:02:29.737
지난 시간에 다양한 딥러닝 최적화 알고리즘을 배웠습니다.

00:02:29.737 --> 00:02:34.975
 SGD, Momentum, Nesterov, RMSProp 그리고 Adam
에 대해서 배웠습니다.

00:02:34.975 --> 00:02:45.492
이 방법들을 모두 기본적인 SGD를 조금씩 변형시킨 방법이었죠
구현 자체는 간단하나 네트워크의 수렴속도는 더 빨랐습니다.

00:02:45.492 --> 00:02:48.529
그리고 regularization에 대해서도 배웠습니다.
특히나 Dropout에 관한 것이었죠

00:02:48.529 --> 00:02:56.975
Dropout은 forward pass에서 임의의 부분은 0으로 만들고
test time에서는 그 noise를 marginalize out 했습니다.

00:02:56.975 --> 00:03:02.805
그리고 딥러닝에서 사용하는 다양한 regularization들의
일반적인 패턴에 대해서는 배웠습니다.

00:03:02.805 --> 00:03:08.415
Train time에 noise를 추가하고, Test time에는
marginalize out 합니다.

00:03:08.415 --> 00:03:15.376
그리고 Transfer learning도 배웠습니다. Pre-train
model을 다운받아서 fine-tune 하는 방법이죠

00:03:15.376 --> 00:03:21.314
Transfer learning을 이용하면 데이터셋이 많지 않더라도
딥러닝 문제를 다룰 수 있는 방법이었습니다.

00:03:22.781 --> 00:03:29.615
이번 시간에는 화제를 조금 돌려서 소프트웨어/하드웨어가 동작하는
방식에 대해서 조금 다배워보도록 하겠습니다.

00:03:29.615 --> 00:03:36.276
그리고 실제 여러분이 학습 시 사용하는 소프트웨어들을
조금 더 심도깊게 다뤄보도록 하겠습니다.

00:03:36.276 --> 00:03:43.967
정리하자면 CPU와 GPU에 대해 조금 배울 것이고, 요즘 사람들이
가장 많이 사용하는 립러닝 프레임워크들에 대해서도 배울 것입니다.

00:03:45.471 --> 00:03:52.961
자 우선 이런 얘기는 수도 없이 많이 들으셨을 것입니다.
컴퓨터에 CPU와 GPU가 있다는 것 말입니다.

00:03:52.961 --> 00:04:02.655
딥러닝에서 GPU를 사용하는데, 사실 지금까지 왜 GPU가 CPU가 좋은지
에 대해서 명확하게 언급하지 않고 넘어갔습니다.

00:04:02.655 --> 00:04:06.472
컴퓨터를 조립해본 적이 있나요?
손 한번 들어보시겠습니까

00:04:06.472 --> 00:04:10.965
한 1/3에서 절반정도는 계시는군요

00:04:10.965 --> 00:04:15.174
이 사진은 제 컴퓨터입니다. 제가 직접 조립했죠

00:04:15.174 --> 00:04:22.261
컴퓨터 내부에서 많은 부품들이 있습니다. 아마 여러분들은 각각이
무엇인지 아실 거라고 믿습니다.

00:04:22.261 --> 00:04:25.594
CPU는 Central Processing Unit이죠

00:04:25.594 --> 00:04:31.391
이 조그마한 칩은 쿨러 믿에 숨어있습니다.

00:04:31.391 --> 00:04:39.555
CPU는 엄청 작습니다. 공간을 크게 잡아먹지 않죠

00:04:39.555 --> 00:04:46.221
자 여기 GPU들을 보면 공간을 엄청 차지하고 있습니다.
괴물같은 녀석들입니다.

00:04:46.221 --> 00:04:50.296
GPU는 자기만의 쿨러가 있고 파워도 엄청 먹고 엄청 큽니다.

00:04:50.296 --> 00:04:59.139
GPU가 얼마나 많은 파워를 사용하는지 그리고 얼마나 큰지는
케이스에서 얼마나 많은 부분을 차지하는지만 봐도 알 수 있습니다.

00:04:59.139 --> 00:05:04.516
GPU란 것이 무엇이길래 딥러닝에서 이렇게 중요한 걸까요

00:05:04.516 --> 00:05:08.937
GPU는 graphics card 혹은
Graphics Processing Unit 이라고 합니다.

00:05:08.937 --> 00:05:16.166
사실 GPU는 computer graphics를 랜더링하기 위해서
만들어졌습니다. 게임같은 것들을 위해서죠

00:05:16.166 --> 00:05:23.247
자 다시한번 손을 들어보죠, 여기에 집에서 컴퓨터게임을
하시는 분들이 계십니까?

00:05:23.247 --> 00:05:25.693
절반정도 있군요 좋습니다.

00:05:25.693 --> 00:05:32.196
게임을 하시거나 컴퓨터를 직접 조립해 보신 분들이라면
다음과 같은 질문에 대한 나름대로의 의견을 가지고 계실 것입니다.

00:05:32.196 --> 00:05:34.095
[웃음]

00:05:34.095 --> 00:05:37.666
이는 Computer Science에서 가장 큰 논쟁 중 하나입니다.

00:05:37.666 --> 00:05:42.620
Intel vs AMD 라던가, 그래픽카드의 경우
NVIDIA vs AMD 같은 논쟁들이죠

00:05:42.620 --> 00:05:45.394
텍스트 에디터의 경우 Vim vs Emacs도 있겠군요

00:05:45.394 --> 00:05:51.945
대부분의 게이머들은 이 논쟁에 대해서
자기만의 의견을 가지고 있습니다.

00:05:51.945 --> 00:05:59.116
딥러닝의 경우는 대부분 한쪽만 선택합니다.
NVIDIA죠

00:05:59.116 --> 00:06:05.117
만약 AMD 카드를 가지고있다면 아마 딥러닝에 사용하는데
문제가 많을 것입니다.

00:06:05.117 --> 00:06:08.812
지난 수년간 NVIDIA는 딥러닝에 많은 공을 들여왔습니다.

00:06:08.812 --> 00:06:11.997
NVIDIA의 전략은 세간의 주목을 받았습니다.

00:06:11.997 --> 00:06:19.354
Nvidia의 많은 엔지니어들은 딥러닝에 적합한 하드웨어를
만들기 위해서 많은 노력을 했습니다.

00:06:19.354 --> 00:06:27.718
그래서 딥러닝과 관련해서는 NVIDIA GPU가
 거의 독점적으로 언급됩니다.

00:06:27.718 --> 00:06:35.268
아마 미래에는 또 다른 후발주자가 생겨날 지 모르겠지만
적어도 지금은 NVIDIA가 독점적입니다.

00:06:35.268 --> 00:06:41.705
자 그럼 CPU와 GPU의 차이는 무엇일까요? 제가 아주 간단하게
표로 한번 정리해 봤습니다.

00:06:41.705 --> 00:06:52.079
위에 두개는 Intel의 최신 상업용 CPU들이고 밑에 두개는
NVIDIA의 최신 상업용 GPU들입니다.

00:06:52.079 --> 00:06:55.975
여기에는 몇 가지 주요 트렌드가 있습니다.

00:06:55.975 --> 00:07:03.284
GPU와 CPU모두 임의의 명령어를 수행할 수 있는
범용 컴퓨팅 머신입니다.

00:07:03.284 --> 00:07:05.987
하지만 이 둘은 질적으로 아주 다릅니다.

00:07:05.987 --> 00:07:16.714
CPU의 경우 core의 수가 적습니다. 오늘날의 상업용 테스크탑 CPU의
경우에 코어가 4개에서 6개 조금 많으면 10개 정도입니다.

00:07:16.714 --> 00:07:24.893
그리고 hyperthreading 기술와 더불어 CPU는 8 ~ 20개의
스레드를 동시에 실행시킬 수 있습니다.

00:07:24.893 --> 00:07:29.700
따라서 CPU는 한번에 20가지의 일(스레드)을
할 수 있는 것입니다.

00:07:29.700 --> 00:07:34.527
이 숫자(20)가 그닥 커보이진 않습니다. 하지만
CPU의 멀티스레드는 아주 강력합니다.

00:07:34.527 --> 00:07:37.223
아주 많은 일을 할 수 있으면 엄청 빠릅니다.

00:07:37.223 --> 00:07:43.011
모든 CPU 명령어들은 정말 많은 일을 할 수 있죠
그리고 아주 독립적으로 수행합니다.

00:07:43.011 --> 00:07:51.909
하지만 GPU의 경우는 상황이 조금 다릅니다.
고성능의 상업 GPU의 경우 수천개의 코어가 있습니다.

00:07:51.909 --> 00:08:00.412
NVIDIA Titan XP와 같은 최상위 모델의 경우 3840개의
코어가 있습니다. 정말 엄청난 수 입니다.

00:08:02.223 --> 00:08:06.357
동일 가격의 CPU를 보면 코어가 10개 뿐이 없죠

00:08:06.357 --> 00:08:12.207
하지만 GPU의 단점을 보자면 각각의 코어가
더 느린 clock speed에서 동작한다는 점입니다.

00:08:12.207 --> 00:08:14.439
그리고 두번째는 그 코어들이 그렇게 많은 일을 할 수 없다는 것입니다.

00:08:14.439 --> 00:08:19.680
사실 CPU와 GPU코어는 1:1로 비교할 수 없습니다.

00:08:19.680 --> 00:08:22.510
GPU코어들은 독립적으로 동작하지 않습니다.

00:08:22.510 --> 00:08:29.297
코어마다 독립적인 테스크가 있는 것이 아니라 많은 코어들이
하나의 테스크를 병렬적으로 수행하는 것이죠

00:08:29.297 --> 00:08:32.405
따라서 코어의 수만 가지고 직접적으로 비교할 수는 없습니다.

00:08:32.405 --> 00:08:41.370
하지만 GPU의 코어의 수가 많다는 것은 어떤 테스크가 있을 때
이 일을 병렬로 수행하기 아주 적합하다는 것은 알 수 있습니다.

00:08:41.370 --> 00:08:44.742
하지만 그 테스크는 전부 같은 테스크여야 할 것입니다.

00:08:44.742 --> 00:08:49.387
그리고 또 한가지 말씀드릴 것은 메모리와 관련된 것입니다.

00:08:49.387 --> 00:08:58.523
CPU에도 캐시가 있습니다. 하지만 비교적 작습니다. CPU는 대부분의
메모리를 RAM에서 끌어다 씁니다.

00:08:58.523 --> 00:09:06.589
RAM은 일반적으로  8, 12, 16, 32 GB 바이트 정도이죠

00:09:06.589 --> 00:09:10.646
반면 GPU는 칩 안에 RAM이 내장되어 있습니다.

00:09:12.055 --> 00:09:22.675
실제 RAM와 GPU간의 통신은 상당한 보틀넥을 초래합니다.
그렇게 떄문에 GPU는 보통 칩에 RAM이 내장되어 있습니다.

00:09:23.955 --> 00:09:33.481
Titan XP의 경우 내장 메모리가 12GB정도 됩니다.

00:09:33.481 --> 00:09:41.790
GPU는 12GB의 메모리와 GPU 코어 사이의 캐싱을 하기 위한
일종의 다계층 캐싱 시스템을 가지고 있습니다.

00:09:41.790 --> 00:09:46.908
이는 CPU의 캐싱 계층구조와 아주 유사합니다.

00:09:47.985 --> 00:09:52.583
CPU는 범용처리에 적합합니다.
CPU는 아주 다양한 일을 할 수 있죠

00:09:52.583 --> 00:09:57.089
그리고 GPU는 병렬처리에 더 특화되어 있습니다.

00:09:57.089 --> 00:10:04.106
GPU에서 정말 잘 동작하고 아주 적합한 알고리즘은 바로
행렬곱(Matrix multiplication) 연산입니다.

00:10:04.106 --> 00:10:14.348
왼쪽 행렬은 행이 엄청 많은 행렬입니다.
오른쪽은 열이 많죠

00:10:14.348 --> 00:10:25.009
오른쪽 행렬은 왼쪽 두 행렬의 내적입니다. 그리고 이 때의
내적 연산은 모두 서로 독립적입니다.

00:10:25.009 --> 00:10:33.653
오른쪽의 결과행렬을 살펴보면 각각의 원소가 전부 독립적입니다.
따라서 모두 병렬로 수행될 수 있습니다.

00:10:33.653 --> 00:10:38.289
그리고 각 원소들은 모두 같은 일을 수행합니다.
두개의 벡터를 내적하는 것이죠.

00:10:38.289 --> 00:10:44.177
다만 서로 입력 데이터만 조금씩 다를 뿐입니다.

00:10:44.177 --> 00:10:55.166
GPU는 결과 행렬의 각 요소들을 병렬로 계산할 수 있으며
이러한 특성 때문에 GPU는 엄청나게 빠릅니다.

00:10:55.166 --> 00:11:04.940
이런 연산들은 GPU가 정말 잘 하는 것들입니다.
CPU였다면 각 원소를 하나씩만 계산할 것입니다.

00:11:06.337 --> 00:11:13.829
물론 CPU라고 그렇게 단순하게 동작하지는 않습니다. CPU는
여러개의 코어가 있고 Vectorized instructions이 존재합니다.

00:11:13.829 --> 00:11:19.568
그렇다고 해도 아주 massive한 병렬화 문제에 대해서는
GPU의 처리량이 압도적입니다.

00:11:19.568 --> 00:11:25.404
가령 행렬의 크기가 엄청 큰 경우가 될 수 있겠습니다. 그리고
비슷한 맥락으로 Convolution의 경우도 같습니다.

00:11:25.404 --> 00:11:36.359
convolution에는 입력(텐서)이 있고 가중치가 있죠 cov 출력은
마찬가지로 입력과 가중치간의 내적입니다.

00:11:36.359 --> 00:11:43.354
GPU의 경우라면 이 연산을 각 코어에 분배시켜서
아주 빠르게 연산할 수 있도록 해줍니다.

00:11:43.354 --> 00:11:49.510
이런 종류의 연산들이 일반적으로 CPU에 비해 GPU에서
연산 속도의 이점을 볼 수 있는 것들입니다.

00:11:51.695 --> 00:11:55.498
여러분들도 직접 GPU에서 실행되는 코드를 작성할 수 있습니다.

00:11:55.498 --> 00:12:03.614
NVIDIA에서 CUDA를 지원하는데 그 코드를 보면 c언어
스럽게 생겼습니다. 하지만 GPU에서 실행되는 코드입니다.

00:12:03.614 --> 00:12:05.484
하지만 CUDA코드를 작성하는 것은 상당히 까다롭습니다.

00:12:05.484 --> 00:12:12.002
GPU의 성능을 전부 짜낼 수 있는 코드를 작성하는 것은
상당히 힘든 일입니다.

00:12:12.002 --> 00:12:19.163
아주 세심하게 메모리구조를 관리해야 합니다. 가령 cache misses나
branch mispredictions 같은 것들을 전부 고려해야 하죠

00:12:19.163 --> 00:12:22.930
따라서 여러분 스스로 아주 효율적인 CUDA code를
작성하는 것은 상당히 어렵습니다.

00:12:22.930 --> 00:12:32.537
때문에 NVIDIA는 GPU에 고도로 최적화시킨 기본연산
라이브러리를 배포해 왔습니다.

00:12:32.537 --> 00:12:40.610
가령 cuBLAS는 다양한 행렬곱을 비롯한 연산들을 제공합니다.
이는 아주 고도로 최적화되어 있습니다.

00:12:40.610 --> 00:12:46.438
이는 GPU에서 아주 잘 동작하고 하드웨어 사용의
이론적 최대치까지 끌어올려 놓은 라이브러리입니다.

00:12:46.438 --> 00:12:54.499
유사하게 cuDNN이라는 라이브러리도 있습니다. 이는 convolution,
forward/backward pass, batch norm, rnn 등

00:12:54.499 --> 00:12:57.454
등 딥러닝에 필요한 거의 모든 기본적인 연산들을
제공하고 있습니다.

00:12:57.454 --> 00:13:03.842
NVIDIA는 자사의 하드웨어에 아주 효율적으로 동작하는
라이브러리를 바이어리로 배포하고 있습니다.

00:13:03.842 --> 00:13:09.624
따라서 실제로는 딥러닝을 위해 CUDA 코드를 직접 작성하는
일은 없을 것입니다.

00:13:09.624 --> 00:13:14.173
이미 다른 사람들이 작성한 코드를 불러와서 쓰기만 하면 됩니다.

00:13:14.173 --> 00:13:19.573
이미 NVIDIA에서 극도로 최적화된 소스를 가져와서 쓰기만 하면 됩니다.

00:13:19.573 --> 00:13:23.693
그리고 또 하나의 언어가 있는데 OpenCL입니다.
OpenCL이 조금 더 범용적입니다.

00:13:23.693 --> 00:13:29.185
NVIDIA GPU에서만 동작하는 것이 아니라 AMD 에서도
그리고 CPU에서도 동작합니다.

00:13:29.185 --> 00:13:43.938
하지만 OpenCL은 아직 딥러닝에 극도로 최적화된 연산이나 라이브러리가
개발되지는 않았습니다. 그래서 CUDA보다는 성능이 떨어집니다.

00:13:43.938 --> 00:13:51.839
미래에는 더 많은 open standard가 생기고 이종 플랫폼 간의
다양한 방법들이 생겨날지도 모르겠지만

00:13:51.839 --> 00:13:55.488
현재로썬 NVIDIA가 딥러닝의 선두주자입니다.

00:13:55.488 --> 00:14:01.686
GPU 프로그래밍을 직접 해보면 익힐 수 있는 다양한 리소스들이 있습니다.
아마 아주 흥미로울 것입니다.

00:14:01.686 --> 00:14:05.900
아주 massive한 병령처리 아키텍쳐를 다루는 것이기 때문에
코드를 작성하는 패러다임도 조금 다를 것입니다.

00:14:05.900 --> 00:14:08.023
하지만 이 이상은 우리 강의의 주제를 벗어나기 때문에
여기까지만 하겠습니다.

00:14:08.023 --> 00:14:12.263
다시한번 만씀드리지만 GPU로의 딥러닝을 위해서
굳이 스스로 CUDA code를 작성할 필요는 없습니다.

00:14:12.263 --> 00:14:16.600
사실 저의 경우도 연구 프로젝트를 진행하면서
CUDA  code를 직접 짜본 적은 없습니다.

00:14:16.600 --> 00:14:22.219
하지만 여러분이 직접 코드를 짜진 않더라도 어떻게 동작하는지
잘 알아두는 것은 상당히 유욜할 수 있습니다.

00:14:23.488 --> 00:14:29.168
실제 CPU와 GPU의 성능을 한번 살펴보고 싶으시다면,
제가 작년 여름이 벤치마크 한 것이 있습니다.

00:14:29.168 --> 00:14:36.065
그때 당시 성능이 꽤 괜찮았던 Intel CPU와
성능이 가장 좋았던 NVIDIA GPU를 비교한 것입니다.

00:14:38.747 --> 00:14:48.954
더 자세한 내용은 제 Github에서 찾아볼 수 있습니다.
VGG16/19와 다양한 ResNets을 이용했는데요

00:14:49.830 --> 00:14:57.114
그리고 그 결과 GPU에서 보통 65에서 75배의 speed up을 볼 수
있었습니다. 완전히 같은 연산량으로 비교한 것이죠

00:14:57.114 --> 00:15:00.984
GPU의 경우는 Pascal TitanX입니다.

00:15:00.984 --> 00:15:08.604
CPU의 경우 최상위 성능 까지는 아니였지만
Intel E5 Processor였습니다.

00:15:08.604 --> 00:15:15.550
하지만 여러분들이 이와 같은 딥러닝 벤치마크를 살펴볼 때
정말 조심해야 할 점이 있습니다.

00:15:15.550 --> 00:15:20.103
왜냐하면 비교군 간에 불공평한 비교를 하기 십상이기 떄문이죠

00:15:20.103 --> 00:15:26.339
따라서 여러분들이 벤치마크를 살펴볼 때 어떤 부분이 불공평한지를
주도면밀하게 살펴볼 필요가 있습니다.

00:15:26.339 --> 00:15:35.855
제가 이 벤치마크에 대해 말씀드려 보자면 이 결과는 사실
CPU에게는 조금 불리합니다.

00:15:35.855 --> 00:15:38.721
제가 CPU에서는 성능을 최대화 시킬 수 있을만큼
공을 들이기 않았기 때문입니다.

00:15:38.721 --> 00:15:42.483
저는 아마 BLAS 라이브러리가 CPU에서 더 잘 동작하도록
수정해 볼 수도 있었을 것입니다.

00:15:42.483 --> 00:15:44.540
그렇게 되면 이 표의 결과 보다는 조금 더 좋았을 것입니다.

00:15:44.540 --> 00:15:51.964
하지만 이또한 뛰어난 성능이긴 합니다. Torch를 설치하고
CPU와 GPU로 실행시켜본 것입니다.

00:15:51.964 --> 00:15:57.872
이 성능도 나쁘진 않지만 CPU의 성능을 극대화 시킨 것은 아닙니다.

00:15:57.872 --> 00:16:02.422
CPU의 성능을 높힐 수 있는 여지는 충분했습니다.

00:16:02.422 --> 00:16:15.543
그리고 또 하나의 벤치마크가 있는데 convolution 연산에
cuDNN을 사용한 것과 일반적인 CUDA를 사용한 것입니다.

00:16:15.543 --> 00:16:17.623
오픈소스 커뮤니티에 게제된 결과입니다.

00:16:17.623 --> 00:16:24.653
여기에서 볼 수 있는 것은 동일한 하드웨어와 동일한 딥러닝
프레임워크를 사용한 것이고 다른 점은 오직

00:16:24.653 --> 00:16:37.442
cuDNN/CUDA 입니다. CUDA은 조금 덜 최적화된 것이죠.
cuDNN이 3X의 속도향상이 있습니다.

00:16:37.442 --> 00:16:45.202
일반적으로 여러분이 GPU 코드를 직접 작성해야 한다면
cuDNN을 반드시 사용해야 할 것입니다.

00:16:45.202 --> 00:16:51.602
cuDNN 라이브러리를 쓰고 안 쓰고의 차이가 거의
3배가 날테니 말입니다.

00:16:51.602 --> 00:17:02.882
그리고 실제로 GPU로 학습을 할 때 생기는 문제 중 하나는 바로 Model과
Model의 가중치는 전부 GPU RAM에 상주하고 있는 반면에

00:17:02.882 --> 00:17:07.243
실제 Train data(Big data)는 SSD와 같은 하드드라이브에
있다는 것입니다.

00:17:07.243 --> 00:17:13.204
때문에 Train time에 디스크에서 데이터를 읽어드리는 작업을
세심하게 신경쓰지 않으면 보틀넥이 발생할 수 있습니다.

00:17:14.321 --> 00:17:23.002
GPU는 forward/backward 가 아주 빠른 것은 사실이지만, 디스크
에서 데이터를 읽어드리는 것이 보틀넥이 되는 것입니다.

00:17:23.002 --> 00:17:25.699
이는 상당히 좋지 않은 상황이고 느려지게 될 것입니다.

00:17:25.700 --> 00:17:31.459
해결책 중 하나는 바로 데이터셋이 작은 경우에는 전체를
RAM에 올려 놓는 것입니다.

00:17:31.459 --> 00:17:36.479
데이터셋이 작지 않더라도, 서버에 RAM 용량이 크다면
가능할 수도 있을 것입니다.

00:17:36.479 --> 00:17:42.917
혹은 HDD대신에 SSD를 사용하는 방법이 있습니다.
데이터를 읽는 속도를 개선시킬 수 있겠죠

00:17:42.917 --> 00:17:52.152
또 한가지 방법은 바로 CPU의 다중스레드를 이용해서 데이터를
RAM에 미리 올려 놓는 것(pre-fetching) 입니다.

00:17:52.152 --> 00:17:57.724
그리고 buffer에서 GPU로 데이터를 전송시키게 되면
성능향상을 기대할 수 있을 것입니다.

00:17:57.724 --> 00:18:08.804
물론 이런 설정 자체가 조금 까다롭긴 하지만 GPU는 빠른데 데이터
전송 자체가 충분히 빠르지 못하면 보틀넥이 생길수 밖에 없습니다.

00:18:08.804 --> 00:18:11.657
이 부분은 항상 인지하고 있어야 합니다.

00:18:11.657 --> 00:18:17.432
지금까지는 딥러닝에 관련한 GPU와 CPU에 대한
간략한 소개였습니다.

00:18:17.432 --> 00:18:21.616
자 이제 조금 주제를 틀어서
소프트웨어와 관련된 이야기를 해볼까 합니다.

00:18:21.616 --> 00:18:25.006
실제로 사람들이 사용하는 딥러닝 프레임워크와 관련된 것입니다.

00:18:25.006 --> 00:18:28.819
다음으로 넘어가기에 앞서 혹시 CPU와 GPU에 관한
질문 있으십니까?

00:18:28.819 --> 00:18:30.519
네 질문있나요?

00:18:30.519 --> 00:18:34.686
[학생이 질문]

00:18:40.961 --> 00:18:45.854
질문은 바로 데이터읽기 에서의 병목을 해결하기 위해서
프로그래밍으로 우리가 할 수 있는 일이 무엇인지 입니다.

00:18:45.854 --> 00:18:50.833
소프트웨어적으로 할 수 있는 일은
CPU에서 미리 불러오는 것입니다.(pre-fetching)

00:18:50.833 --> 00:18:55.054
여러분은 다음과 같이 과정들이 순차적으로 진행되는
것을 원하지 않을 수 있습니다.

00:18:55.054 --> 00:18:58.791
디스크에서 데이터를 읽습니다. 그리고 미니배치의 데이터가
다 읽힐 때 까지 기다립니다.

00:18:58.791 --> 00:19:02.458
그리고 미니배치를 GPU에 전송합니다.
그리고 GPU에서 forward/backward를 수행하고

00:19:02.458 --> 00:19:05.442
그다음 또 다른 미니배치를 읽는 것이죠
이 단계를 전부 순차적으로 진행합니다.

00:19:06.714 --> 00:19:15.469
하지만 그러지 말고 CPU의 멀티스레딩을 통해 디스크에서
데이터를 불러오는 백그라운드 작업을 하고 있다면

00:19:15.469 --> 00:19:17.076
이 과정을 interleave하게 진행할 수 있을 것입니다.

00:19:17.076 --> 00:19:21.506
GPU이 계산을 수행하는 동안 CPU의 백그라운드 스레드는
디스크에서 데이터를 불러옵니다.

00:19:21.506 --> 00:19:28.534
그리고 CPU의 메인 스레드에서 synchronization를 관리하게 되면
이 모든 작업이 병렬로 수행될 수 있을 것입니다.

00:19:28.534 --> 00:19:38.016
그리고 고맙게도 여러분이 딥러닝 프레임워크를 사용한다면 이미 다
구현되어 있습니다. 일일이 구현하기 까다롭기 때문이죠

00:19:38.016 --> 00:19:41.738
딥러닝 프레임워크의 동향을 급변하고 있습니다.

00:19:41.738 --> 00:19:47.915
작년까지만 해도 Caffe, Torch, Tensorflow에 대해서만
언급했었습니다.

00:19:47.915 --> 00:20:00.232
1년 전까지만 해도 Tensorflow가 가장 최신의 프레임워크라서
사람들에게 널리 쓰이지 않았었습니다.

00:20:00.232 --> 00:20:06.310
하지만 지금은 아주 유명해져서 많은 사람들이 선택하는
메인 프레임워크가 되었습니다. 아주 큰 변화입니다.

00:20:07.342 --> 00:20:12.282
그리고 작년부터 정말 많은 프레임워크가 생겨나고 있습니다.

00:20:12.282 --> 00:20:18.052
특히나 Caffe2와 PyTorch가 생겼습니다.Facebook에서 밀고있는
프레임워크로 아주 흥미롭습니다.

00:20:18.052 --> 00:20:20.409
그 밖에도 정말 많은 프레임워크들이 있습니다.

00:20:20.409 --> 00:20:24.089
Baidu에는 Paddle이 있고 Microsoft는 CNTK이 있죠

00:20:24.089 --> 00:20:33.449
Amazon은 주로 MXNet을 사용하는데 이 밖에도 정말 많은
프레임워크가 있습니다. 다 써볼 시간도 부족하죠

00:20:33.449 --> 00:20:43.572
이 그림에서 주목할만한 재미있는 점은 바로 딥러닝 프레임워크의
초창기 세대는 학계(academia)에서 구축되었다는 것입니다.

00:20:43.572 --> 00:20:49.388
원래 Caffe은 Berkeley에서 Torch는 NYU에서 개발되었고
이후 Facebook과 공동연구를 시작한 것이죠

00:20:49.388 --> 00:20:52.077
Theana은 대부분 Montreal에서 개발했습니다.

00:20:52.077 --> 00:20:56.491
하지만 다음세대의 딥러닝 프레임워크들은
기업(industry)에서 태동했습니다.

00:20:56.491 --> 00:21:00.659
Caffe2와 PyTorch는 Facebook에서 나왔습니다.
TensorFlow은 Google에서 나왔죠

00:21:00.659 --> 00:21:08.925
최근 몇 년간의 흥미로운 변화는 바로
academia에서 industry로의 이동이라고 할 수 있습니다.

00:21:08.925 --> 00:21:13.187
이제는 industry가 이러한 강력한 프레임워크를 제공하고 있습니다.

00:21:14.147 --> 00:21:24.850
오늘은 대부분 PyTorch와 TensorFlow에 대해서 말씀드릴 것입니다.
아마 여러분은 앞으로 이 두 프레임워크를 자주 다루게 될 것입니다.

00:21:24.850 --> 00:21:32.192
Caffe와 Caffe2도 간단히 설명해 드리겠습니다.
하지만 깊게 말씀드리진 않을 것입니다.

00:21:32.192 --> 00:21:36.705
우선 더 깊게 들어가기 전에 제가 어떤 것을 주로 사용했는지
미리 말씀드려야 할 것 같습니다.

00:21:36.705 --> 00:21:43.501
저 같은 경우에는 지난 몇 년간 Torch를 이용했습니다.
아주 많이 사용했고 참 좋아하는 녀석입니다.

00:21:43.501 --> 00:21:48.568
그리고 얼마전에 PyTorch로 갈아탔습니다.

00:21:48.568 --> 00:21:52.306
때문에 저 같은 경우 Tensorflow 같은 다른 플랫폼의
경험은 조금 부족합니다.

00:21:52.306 --> 00:21:58.382
그렇지만 저는 여러분들에게 최대한 잘 설명해
드리기 위해 노력 할 것입니다.

00:21:58.382 --> 00:22:06.807
자 그럼 지난 강의를 상기해보면 Computational graphs
에 대해서 수도없이 들었을 것입니다.

00:22:06.807 --> 00:22:13.176
여러분이 딥러닝과 관련된 작업을 한다면
computational graph를 빼고는 아무 것도 할 수 없죠

00:22:13.176 --> 00:22:18.778
여기 선형분류기의 예를 들자면 데이터 X가 있고 가중지 W가 있습니다.
그리고 이를 행렬곱으로 표현하죠

00:22:18.778 --> 00:22:22.832
그리고 hinge loss 같은 손실함수를 계산할 것입니다.

00:22:22.832 --> 00:22:28.909
regularization term도 있겠죠. 이런 모든 것들을 전부
그래프 구조 안에 표현할 수 있습니다.

00:22:28.909 --> 00:22:36.167
Neural net의 경우에 이 그래프구조는 상당히 복잡합니다.
다양한 레이어와 활성함수가 존재할 것입니다.

00:22:36.167 --> 00:22:39.687
그리고 아주 많은 가중치들이 그래프 전역에 퍼져 있을 것입니다.

00:22:39.687 --> 00:22:47.328
Neural turing machines의 그래프는 훨씬 더 복잡합니다.
너무 크고 복잡해서 그릴 수 조차 없습니다.

00:22:48.349 --> 00:22:58.727
따라서 우리가 굳이 코드를 손으로 작성하지 않고 딥러닝 프레임워크
를 이용하는데는 크게 세 가지 이유가 있습니다.

00:22:58.727 --> 00:23:08.610
우선 첫째는 딥러닝 프레임워크를 이용하게 되면 이처럼 엄청 복잡한
그래프를 우리가 직접 만들지 않아도 된다는 것입니다.

00:23:08.610 --> 00:23:13.956
두번째는, 딥러닝에서는 항상 그래디언트를 계산해야 하죠

00:23:14.812 --> 00:23:18.900
Loss를 계산하고 Loss에 부합하는 가중치의 그래디언트를
계산해야 합니다.

00:23:18.900 --> 00:23:26.115
그래디언트가 자동으로 계산되면 정말 좋을 것입니다.
이를 계산하는 코드를 직접 짜고싶은 사람은 없겠죠

00:23:26.115 --> 00:23:36.539
딥러닝 프레임워크를 사용하면 forward pass만 잘 구현해 놓으면
back propagation은 알아서 구성됩니다.

00:23:36.539 --> 00:23:42.000
그리고 마지막 세번째로는 여러분은 GPU을 효율적으로
사용하고 싶을 것입니다.

00:23:42.000 --> 00:23:48.389
하지만 cuBLAS, cuDNN, CUDA 그리고 memory등을 여러분이
직접 세심하게 다루고 싶지는 않을 것입니다.

00:23:48.389 --> 00:23:52.439
이런 모든 것들을 다루는 것은 정말 까다롭고 힘들 일이죠

00:23:52.439 --> 00:23:59.450
이러한 이유 때문에 보통 밑바닥 부터 구현하지 않고
프레임워크를 이용하는 것입니다.

00:23:59.450 --> 00:24:05.231
computational graph의 예를 한번 봅시다.
이런 식으로 엄청 간단한 예제를 만들어 볼 수 있겠죠

00:24:05.231 --> 00:24:13.071
입력으로 X,Y,Z가 있습니다. X와 Y를 곱하면 a가 됩니다.
a와 Z를 더하면 b가 되죠

00:24:13.071 --> 00:24:18.630
그리고 b를 sum out 연산을 이용해 다 더하면 scaler 값
하나가 나옵니다. C 이죠

00:24:18.630 --> 00:24:31.631
여러분들은 아마 지금까지의 코드를 Numpy로 충분히
구현할 수 있다고 생각하실 지도 모릅니다.

00:24:31.631 --> 00:24:41.923
Numpy를 이용해서 random data를 입력으로 해서 적절하게 곱하고
더하고 하면 아주 쉽게 구현할 수 있겠죠

00:24:41.923 --> 00:24:48.355
하지만 X,Y,Z에 대한 그래디언트를 구하는 경우라면
어떨까요?

00:24:48.355 --> 00:24:52.725
여러분이 Numpy로 작성한 경우라면 backward도
여러분이 스스로 작성해야 합니다.

00:24:52.725 --> 00:25:02.859
아마도 여러분들이 과제에서 많이 해보셨겠지만 이 복잡한 수식을
구현하는 것은 아주 까다롭고 고통스러운 일입니다.

00:25:02.859 --> 00:25:05.675
그리고 Numpy의 또 하나의 문제점은 바로 GPU에서
돌아가지 않는다는 것입니다.

00:25:05.675 --> 00:25:14.920
Numpy는 CPU에서만 동작하죠. 그렇게 때문에 Numpy만 쓰는 사람들은
GPU의 참맛을 경험해 보지 못합니다.

00:25:14.920 --> 00:25:19.527
다시 말씀드리자면 그때 그때 마다 그래디언트를 여러분
스스로 계산하는 것은 상당히 힘든 일입니다.

00:25:19.527 --> 00:25:29.047
때문에 딥러닝 프레임워크의 목표는 바로 여러분이 forward
pass 코드를 Numpy 스럽게 작성을 해 놓으면

00:25:29.047 --> 00:25:33.069
GPU에서도 동작하고 그래디언트도 알아서 계산해
주는 것입니다.

00:25:33.069 --> 00:25:36.397
이는 프레임워크가 존재하는 가장 큰 목적입니다.

00:25:36.397 --> 00:25:44.314
Tensorflow의 예를 한번 볼까요? 두 코드의 computational
graph는 동일합니다. forward pass를 한번 봅시다.

00:25:44.314 --> 00:25:52.687
Tensorflow 의 코드를 보면 Numpy의 forward pass 코드와
아주 유사합니다. 곱셈/덧셈 연산을 한번 보시죠

00:25:52.687 --> 00:25:57.623
하지만 Tensorflow에는 여러분을 위해 그래디언트를 계산해 주는
마법의 코드가 있습니다.

00:25:57.623 --> 00:26:02.235
직접 backward pass를 작성 할 필요가 없는 것입니다.
아주 유용한 기능입니다.

00:26:02.235 --> 00:26:08.926
그리고 Tensorflow가 좋은 점 중 하나는 명령어 한 줄이면
CPU/GPU에서 동작하도록 전환 시킬 수 있다는 것입니다.

00:26:08.926 --> 00:26:16.668
forward pass에서 이런 식으로 선언을 해주면
난 CPU로 돌리고 싶어! 라고 하는것 과 같죠

00:26:16.668 --> 00:26:24.866
그리고 단지 'C'를 'G'로 바꾸기만 하면 이 코드는 GPU에서
동작하게 됩니다.

00:26:24.866 --> 00:26:31.388
코드 몇 줄만 추가해 주면 두 가지 문제를 해결할 수 있는 것입니다.
우선 GPU를 사용할 수 있게 해주는 것이고

00:26:31.388 --> 00:26:35.685
모든 그레디언트를 계산할 수도 있습니다.
아주 큰 장점입니다.

00:26:35.685 --> 00:26:38.459
PyTorch도 거의 비슷합니다.

00:26:38.459 --> 00:26:42.509
PyTorch의 예로 다시한번 살펴보자면 여기에 번수를 선언하고

00:26:42.509 --> 00:26:49.262
forward pass를 진행합니다. 여기에서도 Numpy에서의 코드와
아주 유사하다는 것을 알 수 있습니다.

00:26:49.262 --> 00:26:56.251
PyTorch에서도 한줄이면 그레디언트를 계산할 수 있습니다.

00:26:56.251 --> 00:27:06.781
PyTorch에서 GPU를 사용하려면 CUDA data type으로 선언하기만
하면 됩니다. 그러면 GPU에서 연산이 수행되죠

00:27:06.781 --> 00:27:13.878
지금까지 세가지 예제를 살펴보았습니다.
Numpy, Tensorflow, PyTorch 입니다.

00:27:13.878 --> 00:27:20.564
TensorFlow와 PyTorch모두 Numpy 스럽게 생겼습니다.

00:27:20.564 --> 00:27:24.349
Numpy가 사용하기 쉽기 때문에 Numpy처럼 생긴 것은
정말 좋은 특성입니다.

00:27:24.349 --> 00:27:29.192
Numpy스럽게 생겼을 뿐만 아니라 그레디언트도 알아서 계산해주고
GPU에서도 자동으로 돌아갑니다.

00:27:30.186 --> 00:27:37.502
간략한 소개가 끝났으니 이제 TensorFlow에 대해서 좀더
심도깊게 알아보도록 하겠습니다.

00:27:37.502 --> 00:27:50.662
앞으로는 이 한가지 예시를 가지고 계속 말씀드릴 것입니다.
두개의 fc layer + ReLU를 학습시키는 네트워크 입니다.

00:27:50.662 --> 00:27:55.289
그리고 손실함수로는 L2 Euclidean 를 사용합니다.

00:27:55.289 --> 00:28:08.966
이 네트워크의 성능을 기대하긴 어렵겠지만 딥 러닝을 프레임워크를
설명하는데는 아주 재격입니다. 슬라이드 크기에도 딱 맞죠

00:28:08.966 --> 00:28:15.900
그리고 이 코드는 numpy와 tensorflow를
이미 import한 것입니다.

00:28:15.900 --> 00:28:21.163
Tensorflow의 경우에는 코드가 크게 두가지 스테이지로 나뉩니다.

00:28:21.163 --> 00:28:28.363
상단에 빨간색 박스를 보시죠. 우선은 computational graph
를 정의하는 코드를 작성할 것입니다.

00:28:28.363 --> 00:28:32.360
그래프를 정의하고 나면 그래프를 실행시켜야겠죠

00:28:32.360 --> 00:28:36.851
우선 연산을 수행하려면 데이터를 넣어줘야 합니다.

00:28:36.851 --> 00:28:40.961
Tensorflow를 사용할 때 아주 일반적인 순서가 되겠습니다.

00:28:40.961 --> 00:28:46.615
우선 그래프를 구성하는 코드를 한번 작성해 놓으면 여러분은
이 그래프를 언제든지 실행시킬 수 있습니다.

00:28:48.099 --> 00:28:52.763
그래프를 구성하는 부분을 좀 더 자세히 살펴봅시다.

00:28:52.763 --> 00:29:00.709
우선 맨 처음에 X,Y,w1,w2를 정의합니다. 이들은 모두
tf.placeholder 객체입니다.

00:29:01.637 --> 00:29:05.193
이들은 그래프의 입력노드가 됩니다.

00:29:05.193 --> 00:29:15.379
computational graph의 입구 역할을 하는 것이죠. 가령 데이터를
넣어주고자 하면 이쪽으로 넣어주면 됩니다.

00:29:15.379 --> 00:29:21.944
하지만 이 부분에서 실제적인 메모리할당이 일어나진 않습니다.
단지 그래프만 구성하는 것입니다.

00:29:23.272 --> 00:29:28.665
앞서 선언한 입력 변수를 이용해 봅시다.

00:29:28.665 --> 00:29:37.135
이 변수들을 기반으로  다양한 Tensorflow 연산을 수행합니다.
여러분이 만들고 싶은 연산을 구성할 수 있는 것이죠

00:29:37.135 --> 00:29:46.109
이 예제의 경우 X와 w1의 행렬곱 연산을 수행합니다. 그리고
tf.maximum을 이용해 ReLU를 구현할 수 있습니다.

00:29:46.109 --> 00:29:49.240
그리고 행렬곱 연산을 한번 더 수행해서 그래프의 최종
출력값을 계산할 수 있습니다.

00:29:49.240 --> 00:29:58.175
그리고 예측값과 정답값 Y의 유클리디안 거리를 계산하기
위해서도 Tensor operation 을 사용합니다.

00:29:58.175 --> 00:30:05.824
한가지 주목할 점은 이 코드는 현재 아무런 연산도 수행하지 않는다는
점입니다. 아직까지는 계산할 데이터가 존재하지 않습니다.

00:30:05.824 --> 00:30:15.001
지금은 단지 그래프 구조만 만들어 놓아서 실제 데이터가
들어왔을때 어떻게 연산을 진행해야 하는지만 구성합니다.

00:30:15.001 --> 00:30:18.648
그래프를 구성하는 것 외에 아무 일도일어나지 않습니다.

00:30:18.648 --> 00:30:33.135
그리고 이 빨간색 라인을 통해 로스를 계산하고 w1과 w2의 그레디언트를
계산해주는 마법적일 일을 할 수 있습니다.

00:30:33.135 --> 00:30:37.981
이를 통해 여러분이 과제에서 했던  backporp 코드를
직접 짜는 수고를 덜 수 있습니다.

00:30:37.981 --> 00:30:40.439
이 때도 사실은 실제 계산이 이루어지지는 않습니다.

00:30:40.439 --> 00:30:51.108
여기에서도 단지 그래프에 그레디언트를 계산해 주는
추가적인 연산을 더해준 것과 같습니다.

00:30:51.108 --> 00:31:01.421
자 지금까지는 computational graph를 구성했습니다. Loss와
그레디언트를 계산하기 위한 커다란 그래프를 만든 것이죠

00:31:01.421 --> 00:31:06.843
자 이제 Tensorflow session으로 들어가 봅시다. 이를 통해 실제
그래프를 실행시키고 데이터를 넣어줄 수 있습니다.

00:31:06.843 --> 00:31:13.859
session에 진입하려면 그래프에 들어갈 실제 값을
만들어 줘야 합니다.

00:31:13.859 --> 00:31:19.459
대부분의 경우 TensorFlow는 Numpy arrays
이용할 수 있습니다.

00:31:19.459 --> 00:31:30.226
이 경우에도 Numpy를 이용해서 x,w1,w2,y의 값을
할당해 줬습니다.

00:31:30.226 --> 00:31:32.743
그리고 바로 이 부분이 실제로 그래프를 실행시키는 부분입니다.

00:31:32.743 --> 00:31:38.120
session.run 을 이용해서 그래프의 일부를 실행시킬
수 있습니다.

00:31:38.120 --> 00:31:43.899
첫 번째 인자가 loss라는 것은, 우리가 그래프의 출력으로 어떤
부분을 원하는 지를 말해주는 부분입니다.

00:31:43.899 --> 00:31:50.950
예제에서는 우리가 현재 Loss와 grad1과 grad2를 계산하기를
원한다고 말해주고 있는 것입니다.

00:31:50.950 --> 00:31:57.140
그리고 feed_dict를 통해 실제 값을 전달해 주는 것이죠

00:31:57.140 --> 00:32:06.541
이 한 줄만 실행시키면 그래프가 실행되고
loss와 grad1과 grad2가 계산됩니다.

00:32:06.541 --> 00:32:12.003
그리고 출력 값들도 Numpy array입니다.

00:32:12.003 --> 00:32:19.859
output이란 변수를 나눠보게 되면  loss와 gradient가
Numpy array의 형태로 반환됨을 알 수 있습니다.

00:32:19.859 --> 00:32:23.697
이 값을 가지고 여러분이 원하는 작업을 하시면 됩니다.

00:32:23.697 --> 00:32:29.599
이 경우에는 그래프에서 단 한 번의 forward/backward pass
를 수행한 경우 입니다.

00:32:29.599 --> 00:32:33.167
네트워크를 학습시키기 위해서는 단 몇 줄이면 충분합니다.

00:32:33.167 --> 00:32:45.511
그래프를 여러번 실행시키기 위해서는 for loop를 사용하면 됩니다.
반복적으로 session.run을 호출해 loss와 grad를 계산합니다.

00:32:45.511 --> 00:32:52.291
이 예제에서는 가중치를 업데이트하기 위해 그레디언트를 계산해서
"수동으로(manual)"  Gradient diescent를 하고 있습니다.

00:32:52.291 --> 00:33:00.749
이 코드의 loss를 계산해 보면 loss가 아주 잘 내려가며 이는
네트워크의 학습이 이루어지는 것입니다. 아주 잘 동작하는 것이죠

00:33:00.749 --> 00:33:06.113
이 예제는 TensorFlow에서 네트워크가 어떤 식으로 학습이 되는지를
아주 명확하게 알려줍니다.

00:33:06.113 --> 00:33:08.046
하지만 여기에는 문제가 하나 있습니다.

00:33:08.046 --> 00:33:15.086
forward pass에서 그래프가 실행될 때마다
가중치를 넣어줘야 합니다.

00:33:15.086 --> 00:33:18.835
현재 Numpy로 된 가중치를 가지고 있고 이 값을
그래프에 넣어줘야 하는 것이죠

00:33:18.835 --> 00:33:26.339
그래프가 한번 실행되고 나면 그레디언트를 반환해 줬습니다.
그레디언트는 가중치와 동일한 크기의 행렬이었죠

00:33:26.339 --> 00:33:32.665
이것이 의미하는 바는 여러분이 그래프를 실행할 때 마다 우선
Numpy array로 된 가중치행렬을 Tensorflow로 복사합니다.

00:33:32.665 --> 00:33:36.419
그리고 gradient를 계산해서는 gradient행렬을
Tensorflow에서 Numpy array로 반환해 주는 것이죠

00:33:36.419 --> 00:33:39.849
만약 코드가 CPU에서 실행이 되고 있다면
큰 문제가 되지는 않을 것입니다.

00:33:39.849 --> 00:33:47.235
하지만 지난번에 GPU의 보틀넥에 대해서 말씀드린 적이 있었죠.
 CPU/GPU 메모리산의 데이터교환을 상당히 비용이 큽니다.

00:33:47.235 --> 00:33:59.256
때문에 여러분의 네트워크가 엄청 크고 가중치가 엄청 많다면 이런
GPU/CPU간의 데이터 교환은 이는 엄청 느리고 비용이 클 것입니다.

00:33:59.256 --> 00:34:01.689
이것은 안좋은 일입니다. 바꿀 필요가 있습니다.

00:34:01.689 --> 00:34:06.027
TensorFlow에는 이에 대한 해결책이 있습니다.

00:34:06.027 --> 00:34:17.969
굳이 매번 placeholder를 써서 가중치를 넣어 줄 필요는 없습니다.
대신에 단지 variables로 선언하는 것이죠

00:34:17.969 --> 00:34:27.346
variable은 computational graph안에 서식하는 변수입니다.
그래프가 실행될 때 마다 지속적으로 그래프 안에 상주합니다.

00:34:27.347 --> 00:34:33.094
따라서 w1와 w2를 placeholder 대신에 variables로
선언해 줍니다.

00:34:33.094 --> 00:34:39.219
하지만 이제는 그들이 그래프 안에 살기 때문에 우리는
Tensorflow에게 어떻게 초기화시킬 것인지를 알려줘야 합니다.

00:34:39.219 --> 00:34:44.606
왜냐하면 기존에 그래프 밖에 있을때는 그래프에 넣어주기 전에
Numpy를 이용해서 초기화 시켜주면 그만이었습니다.

00:34:44.606 --> 00:34:50.569
하지만 지금은 그래프 안에 있기 때문에 그 변수들을 초기화시킬
권한은 TensorFlow에 있는 것입니다.

00:34:50.569 --> 00:34:53.149
따라서 우리는 tr.randomnormal 이 필요합니다.

00:34:53.149 --> 00:35:00.627
사실 저 명령어 자체가 변수들을 초기화시켜 주는 것은 아니고
Tensorflow에게 어떻게 이 값들이 초기화되야 하는지는 알려줍니다.

00:35:00.627 --> 00:35:03.215
이 부분에서 다소 헷갈릴 수 있습니다.

00:35:04.869 --> 00:35:11.862
자 그리고 이전 예제에서는  computational graph의
외부에서 가중치를 업데이트했습니다.

00:35:11.862 --> 00:35:17.219
이전 예제에서는 그레디언트를 계산하고 Numpy array로
가중치를 업데이트 했죠

00:35:17.219 --> 00:35:20.264
그리고 계산된 가중치를 다음 스텝에 다시 넣어줬습니다.

00:35:20.264 --> 00:35:29.402
하지만 가중치를 그래프 안에서 업데이트 하기 위해서는 그
연산 자체가 그래프에 포함되어야 합니다.

00:35:29.402 --> 00:35:37.020
이를 위해 assign 함수를 이용합니다. 이를 통해 변수가
그래프 내에서 업데이트가 일어나도록 하는 것입니다.

00:35:37.020 --> 00:35:41.487
이 업데이트된 값들은 항상 그래프 내부에 존재하게 됩니다.

00:35:41.487 --> 00:35:45.976
자 이제 실제 학습을 진행하기에 앞서
(tf.global_variables_initializer() 를 통해)

00:35:45.976 --> 00:35:53.825
그래프 내부의 변수들을 초기화시켜 주는 명령어가 필요합니다.

00:35:53.825 --> 00:35:58.574
초기화가 끝나면 이제 그래프를 돌릴 차례입니다.

00:35:58.574 --> 00:36:05.091
이 코드를 보시면 이제는 데이터와 레이블만 넣어주면 되고
가중치는 그래프 내부에 항상 상주하게 됩니다.

00:36:05.091 --> 00:36:09.517
여기에서는 Tensorflow에게 loss를 계산해 달라고 하는 것이죠

00:36:09.517 --> 00:36:13.001
여러분은 이 코드로 네트워크를 학습시킬 수 있다고
생각할 수 있습니다.

00:36:13.001 --> 00:36:19.964
하지만 사실 이 코드에는 버그가 있습니다. 이 코드를 돌려서
Loss를 그려보면 전혀 학습이 안됩니다.

00:36:19.964 --> 00:36:23.401
도대체 왜 이런 것일까요?

00:36:23.401 --> 00:36:29.957
assign 코드도 작성했고 그래프를 돌려서 Loss와 그레디언트도
계산을 잘 했는데 Loss가 변하질 않습니다. 왜 그럴까요?

00:36:29.957 --> 00:36:31.460
아시는 분 계신가요?

00:36:31.460 --> 00:36:34.595
[학생이 대답]

00:36:34.595 --> 00:36:44.979
대답은 가중치가 매번 초기화되기 때문이라는 것인데요
그럴듯한 답변이지만 사실 그 문제는 아닙니다.

00:36:44.979 --> 00:36:48.057
[학생이 대답]

00:36:48.057 --> 00:36:56.318
대답은 바로 우리가 Tensorflow에게 w1과 w2를 업데이트하라고
명시적으로 말해 줘야 한다는 것입니다.

00:36:56.318 --> 00:36:58.835
그래서 우리는이 큰 계산 그래프 데이터를 구축했습니다.

00:36:58.835 --> 00:37:01.699
구조를 메모리에 저장하고 실행을 호출 할 때,

00:37:01.699 --> 00:37:04.894
우리는 TensorFlow에게 우리가
손실을 계산하기를 원한다고 말했습니다.

00:37:04.894 --> 00:37:09.155
그리고 그래프 내부의 dependence를 고려 해 보면

00:37:09.155 --> 00:37:13.715
Loss를 계산하기 위해서 굳이 업데이트를 할 필요는
없다는 것을 알게 될 것입니다.

00:37:13.715 --> 00:37:21.496
Tensorflow는 아주 스마트하기 때문에 output 에 필요한
연산만 수행합니다.

00:37:21.496 --> 00:37:26.656
이는 Tensorflow의 장점이기도 합니다. 필요한 부분만
실행하기 때문이지요

00:37:26.656 --> 00:37:32.739
하지만 간혹 이 특성이 우리를 헷갈리게 하고
예상하지 못했던 결과를 초래할 수도 있습니다.

00:37:32.739 --> 00:37:39.141
이 경우에는 우리가 Tensorflow에게 업데이트를 수행하라고
명시적으로 말해줘야 합니다.

00:37:39.141 --> 00:37:49.531
해결법 중 하나는 new_w1과 new_w2 를 출력으로 추가하면 됩니다.

00:37:49.531 --> 00:37:57.366
하지만 new_w1과 new_w2의 사이즈가 큰 tensor라면은
상황이 안좋아집니다.

00:37:58.891 --> 00:38:05.138
Tensorflow에게 출력을 요청하는 것은 매 반복마다
CPU/GPU간의 데이터 전송이 요구되기 때문입니다.

00:38:05.138 --> 00:38:07.316
이는 좋지 않죠

00:38:07.316 --> 00:38:11.742
이 대신에 할 수 있는 트릭이 하나 있습니다.
더미 노드(dummy node)하나를 그래프에 추가하는 것이죠

00:38:11.742 --> 00:38:20.307
이 fake data dependencies를 이용하면
 new_w1과 new_w2를 업데이트 할 수 있습니다.

00:38:20.307 --> 00:38:25.803
그리고 그래프를 실행시키면 loss와 더미노드를
계산하게 됩니다.

00:38:25.803 --> 00:38:38.468
이 더미노드는 아무 것도 반환하지 않지만 dependence를 만들었기
때문에 이를 통해 가중치가 업데이트 될 수 있는 것입니다.

00:38:38.468 --> 00:38:39.551
질문있나요?

00:38:40.788 --> 00:38:44.955
[학생이 질문]

00:38:45.854 --> 00:38:51.370
질문은 바로 X와 Y도 그래프에 넣어주지 않은 이유는 무엇인지 입니다.
이들은 여전히 Numpy array로 되어있죠

00:38:51.370 --> 00:38:57.151
이 예제에서는 우리가 매 반복시마다 동일한 X와 Y를
재사용 하고 있습니다.

00:38:57.151 --> 00:39:10.122
이 경우에는 넣어줘도 되겠죠 하지만 좀 더 현실적인 경우에는 X와 Y는
미니배치입니다. 매번 바뀌기 때문에 매번 넣어줘야 합니다.

00:39:10.122 --> 00:39:14.330
이 예제에서는 그래프 안에 넣어주는 것이 가능할 지 모르겠으나
실제로 대부분의 경우에는 그렇지 않습니다.

00:39:14.330 --> 00:39:17.913
대부분의 경우에는 X,Y가 그래프 안에 있으면 안좋습니다.

00:39:19.388 --> 00:39:21.290
다른 질문 있나요?

00:39:21.290 --> 00:39:25.457
[학생이 질문]

00:39:37.046 --> 00:39:44.305
현재 우리는 TensorFlow에게 loss와 update를 출력하길 원한다고
말했습니다.

00:39:44.305 --> 00:39:51.801
사실 Updates는 어떤 값이 아니죠.
따라서 Updates는 None을 반환합니다.

00:39:51.801 --> 00:39:57.416
하지만 dependence로 인해 updates를 통해
assign operations을 수행할 수 있는 것이죠

00:39:57.416 --> 00:40:02.358
그리고 assign operations은 그래프 내부에 있기 때문에
전부 GPU 메모리 내에 상주하는 녀석들입니다.

00:40:02.358 --> 00:40:10.190
덕분에 GPU 내부에서 업데이트를 진행하면서 이 결과를
다시 CPU로 가져오지 않아도 되도록 해 주는 것입니다.

00:40:11.723 --> 00:40:15.112
[학생이 질문]

00:40:15.112 --> 00:40:18.195
질문은 tf.group은 왜 none을 반환하는지 입니다.

00:40:18.195 --> 00:40:25.923
여기에는 TensorFlow의 트릭이 숨어있습니다.
tf.group자체는 어떤 복잡한 TensorFlow 객체를 반환합니다.

00:40:25.923 --> 00:40:32.658
그래프를 구성하기 위해 필요한 일종의 TensorFlow 노드
객체를 반환하는 것이죠

00:40:32.658 --> 00:40:43.333
그리고 session.run를 통해서 그래프를 실행하면
그때 바로 none을 반환하는 것입니다.

00:40:43.333 --> 00:40:45.482
따라서 TensorFlow로 작업 할 때마다

00:40:45.482 --> 00:40:53.487
그래프를 구성하는 동안 반환되는 값들은
Tensorflow 노드와 관련된 어떤 객체들이고

00:40:53.487 --> 00:40:55.466
실제 그래프를 돌려야 실제 원하는 값들이 나오는 것입니다.

00:40:55.466 --> 00:40:59.967
여기에서는 update를 돌리면 출력으로 none이 나오는 것입니다.
이해가 좀 되셨나요?

00:40:59.967 --> 00:41:04.134
[학생이 질문]

00:41:18.796 --> 00:41:22.334
질문은 바로 loss는 왜 값이 있고
updata는 none인지 입니다.

00:41:22.334 --> 00:41:24.068
이는 바로 update가 동작하는 방식에 있습니다.

00:41:24.068 --> 00:41:30.176
loss의 경우에는 우리가 계산하는 실제 값이 있습니다.

00:41:30.176 --> 00:41:35.753
반면 update는 특수한 데이터 타입입니다. 실행을 해도
값을 반환하지 않습니다. none을 반환하죠

00:41:35.753 --> 00:41:38.703
이는 일종의 TensorFlow의 마법이라고 할 수 있습니다.

00:41:38.703 --> 00:41:40.602
아직 헷갈리면 수업이 끝나고 조금 더 말해보죠

00:41:40.602 --> 00:41:42.678
[학생이 질문]

00:41:42.678 --> 00:41:46.186
네 맞습니다. update의 마법은
group이라는 함수에서 비롯되는 것입니다.

00:41:46.186 --> 00:41:52.492
assign operations를 위해서 tf.group을 이용하는 것은
사실 정상적인 방법은 아닌 것 같아 보입니다.

00:41:52.492 --> 00:42:00.004
별로 좋은 방법은 아닌 것 같습니다. 하지만 고맙게도
Tensorflow는 이런 일들을 해주는 편리한 명령어들을 제공해 줍니다.

00:42:00.004 --> 00:42:01.706
이는 바로 optimizer입니다.

00:42:01.706 --> 00:42:06.047
여기 보시면 tf.train.GradientDescentOptimizer
라는 것을 사용하고 있습니다.

00:42:06.047 --> 00:42:08.458
learning rate도 정해줍니다.

00:42:08.458 --> 00:42:12.784
여기에 Adam이나 RMSprop같은
다양한 oprimization 알고리즘이 있는 것입니다.

00:42:12.784 --> 00:42:16.284
자 이제 optimizer.minimize(loss)
를 호출합니다.

00:42:17.311 --> 00:42:21.204
이는 아주 마법같은 일을 합니다.

00:42:21.204 --> 00:42:30.586
이는 w1과 w2가 학습 가능하다는 것을 인식합니다.
optimizer.minimize의 내부를 살펴보면

00:42:30.586 --> 00:42:35.184
그래프에 w1와 w2의 그레디언트를 계산하는 노드도 추가하고

00:42:35.184 --> 00:42:42.219
그리고 w1와 w2의 update operation도 추가합니다.  그리고
assigns을 위한 grouping operation도 있죠

00:42:42.219 --> 00:42:44.206
이 같은 많은 일들이 내부적으로 수행됩니다.

00:42:44.206 --> 00:42:53.518
이 함수를 들여다보면 tf.group도 들어가 있는 등
이전에 봤던 예제와 유사한 일을 하는 것입니다.

00:42:53.518 --> 00:43:00.004
자 이제 반복문을 살펴보면 매 반복마다
loss와 update를 계산해 달라고 하고 있죠

00:43:00.004 --> 00:43:07.450
매번 그래프에게 update를 계산하라고 할 때 마다
update가 일어납니다.

00:43:07.450 --> 00:43:08.593
질문 있나요?

00:43:08.593 --> 00:43:10.959
[학생이 질문]

00:43:10.959 --> 00:43:14.249
질문은 tf.GlobalVariablesInitializer
가 무엇인지 입니다.

00:43:14.249 --> 00:43:20.502
이는 w1과 w2를 초기화시켜 줍니다. 이 변수들을
그래프 내부에 존재하기 때문이죠

00:43:20.502 --> 00:43:37.733
앞서 tf.variable을 만들때 tf.randomnormal이 들어갔죠
tf.GlobalVariablesInitializer이 실제 초기화를 합니다.

00:43:37.733 --> 00:43:40.794
[학생이 질문]

00:43:40.794 --> 00:43:42.271
잘 못들었습니다?

00:43:42.271 --> 00:43:45.233
[학생이 질문]

00:43:45.233 --> 00:43:51.385
placeholder는 그래프 밖에서 데이터를 넣어주는 변수이고
variable은 그래프 내부에 있는 변수입니다.

00:43:51.385 --> 00:44:00.384
저도 자세한 것은 모르지만
코드를 살펴보거나 관련 문서를 참고하시기 바랍니다.

00:44:00.384 --> 00:44:06.130
자 지금까지는 TensorFlow로 네트워크를 학습시키는
전체 예제를 한번 살펴보았고

00:44:06.130 --> 00:44:09.328
이를 더 편리하게 하는 몇 가지 요소들도 살펴보았습니다.

00:44:09.328 --> 00:44:16.954
이전 까지의 예제에서는 loss를 계산하는 연산을 우리가 직접
만들었습니다.

00:44:16.954 --> 00:44:20.739
하지만 TensorFlow에서 제공하는 기본적인
tensor operations을 이용하는 방법도 있습니다.

00:44:20.739 --> 00:44:26.734
Tensorflow는 일반적인 Neural network 모델에 들어가는
편리한 함수들을 제공합니다.

00:44:26.734 --> 00:44:30.040
tf.losses.mean_squared_error를
사용할 수 있습니다.

00:44:30.040 --> 00:44:36.273
이 tensor operations를 사용하면
L2 loss를 직접 구현하지 않아도 됩니다.

00:44:36.273 --> 00:44:46.667
그리고 귀찮은 것이 하나 더 있습니다. 입력과 가중치를 정의하고
행렬 곱연산으로 둘을 묶는 그런 일들입니다.

00:44:46.667 --> 00:44:54.291
이 예제에서는 bias도 넣지 않았습니다. bias를
넣을라 치면 bias를 또 초기화시켜 줘야 하고

00:44:54.291 --> 00:44:58.494
적절한 shape으로 맞춰도 줘야 하고 또 출력 값에
맞춰서 broadcasting이 필요할 수도 있을 것입니다.

00:44:58.494 --> 00:45:01.966
bias 하나를 추가하는데도
엄청나게 많은 코드가 필요한 것이죠

00:45:01.966 --> 00:45:03.664
이를 일일이 다 작성하는 것은 불편합니다.

00:45:03.664 --> 00:45:09.653
여러분이 딥러닝에 들어가는 기본적인 레이어들 가령
convolutions이나 batch norm을 구현하려 치면

00:45:09.653 --> 00:45:17.403
입/출력도 선언해 줘야하고 이를 모두 묶어서
computational graph를 만들어줘야 하는데

00:45:17.403 --> 00:45:22.954
이의 가중치를 초기화시켜주고 shape를 잘 맞춰주는 일은
정말로 성가신 일입니다.

00:45:22.954 --> 00:45:30.615
이에 TensorFlow를 warpping한
 higher level libraries이 존재합니다.

00:45:30.615 --> 00:45:35.965
TensorFlow에서 제공해주는 것중 하나는
tf.layers입니다.

00:45:35.965 --> 00:45:44.060
이 예제를 보시면 X와 Y만 placeholders로 선언해 줍니다.

00:45:44.060 --> 00:45:53.036
그리고 밑에 줄을 보시면 h = tf.layers라고 선언합니다.
그리고 inputs = x, units = H 를 넣어줍니다.

00:45:53.036 --> 00:45:55.171
이 또한 아주 마법스러운 것입니다.

00:45:55.171 --> 00:46:07.411
내부적으로 w1과 b2을 variables로 만들어주고 그래프 내부에
적절한 shape으로 선언해 줍니다. 다만 우리에게는 보이지 않습니다.

00:46:07.411 --> 00:46:12.931
그리고 xavier initialize 객체를 사용하여
어떻게 초기화시킬 것인지를 말해줍니다.

00:46:12.931 --> 00:46:17.200
기존에는 tf.randomnormal을 사용해서
일일이 초기화시켜 줬었죠

00:46:17.200 --> 00:46:22.266
하지만 지금은 이 모든 것들을 알아서 해줍니다.
그리고 h가 출력으로 나옵니다.

00:46:22.266 --> 00:46:27.515
다음 레이어를 보면 이전 레이어 출력인 h를 받아서
똑같은 일을 해줍니다.

00:46:28.487 --> 00:46:36.910
그리고 여기 activation=tf.nn.relu 를 볼 수 있는데 이를
통해 레이어에 relu를 추가해 줄 수 있습니다.

00:46:36.910 --> 00:46:41.370
이처럼 이들은 모두 우리를 대신에서 대부분의 아키텍쳐와
관련된 세부사항들을 다뤄줍니다.

00:46:41.370 --> 00:46:42.784
질문 있나요?

00:46:42.784 --> 00:46:46.446
[학생이 질문]

00:46:46.446 --> 00:46:51.168
질문은 xavier initializer이 특정 분포를
기본값으로 정해 놓고 있는지 입니다.

00:46:51.168 --> 00:46:55.850
아마도 기본값이 있긴 할테지만 정확히는 모르겠네요
관련 문서를 한번 찾아보시기 바랍니다.

00:46:55.850 --> 00:46:58.010
관련 문서를 찾아보는 것은 좋은 전략이 될 수 있습니다.

00:46:58.010 --> 00:47:04.111
가중치 초기화를 더 잘해서 모델이 이전보다 더 빨리
수렴할 수도 있다면 말이죠

00:47:04.111 --> 00:47:11.911
우리는 단지 tf.layers만 두번 호출했을 뿐인데 모델을
구출할 수 있었습니다. 세부사항을 다 기술할 필요가 없죠

00:47:11.911 --> 00:47:14.273
이는 정말 편리합니다.

00:47:14.273 --> 00:47:18.682
하지만 우리가 쓸 수 있는 라이브러리가
tf.contrib.layer만 있는 것은 아닙니다.

00:47:18.682 --> 00:47:23.349
TensorFlow를 기반으로 한 아주 다양한
higher level libraries가 있습니다.

00:47:23.349 --> 00:47:26.841
다양한 라이브러리가 존재하는 이유는 computational graph가
 상대적으로 low level이기 때문입니다.

00:47:26.841 --> 00:47:30.315
computational graph는 상대적으로 low level입니다.

00:47:30.315 --> 00:47:36.426
반면 우리가 Neural network를 다룰때면 레이어와 가중치에 대한
개념을 다룹니다.

00:47:36.426 --> 00:47:41.866
따라서 자연스럽게 computational graph 에 대한 조금은 더
higher level인 추상화 모델을 생각해 볼 수 있을 것입니다.

00:47:41.866 --> 00:47:48.503
다양한 라이브러리와 패키지들은 여러분이 조금 더
높은 추상화레벨에서 작업할 수 있도록 돕는 것입니다.

00:47:48.503 --> 00:47:52.460
아주 유명한 패키지 하나를 더 소개시켜 드리겠습니다
Keras입니다.

00:47:52.460 --> 00:48:02.806
Keras은 아주 훌륭한 APUI로 TensorFlow를 backend로
해서 computational graph를 알아서 만들어줍니다.

00:48:02.806 --> 00:48:07.704
Keras는 Theano backend도 지원합니다.

00:48:07.704 --> 00:48:10.958
이 예제에서 레이어의 시퀀스로 모델을 구성하는
것을 보실 수 있습니다.

00:48:10.958 --> 00:48:17.910
그리고 optimizer 객체를 만들고 model.compile을 하면
그래프가 알아서 만들어지는 마법이 이루어지는 것이죠

00:48:17.910 --> 00:48:22.797
그리고 model.fit을 하게되면 전체 학습과정이
알아서 진행됩니다.

00:48:22.797 --> 00:48:28.523
저는 더 자세한 내용은 잘 모르지만  Keras는 아주 유명하고
여러분이 TensorFlow관련된 일을 한다면 써볼만합니다.

00:48:29.797 --> 00:48:31.270
질문있나요?

00:48:31.270 --> 00:48:35.437
[학생이 질문]

00:48:41.717 --> 00:48:45.525
질문은 바로 여기에는 왜 CPU/GPU에 대한
명시가 없댜는 것입니다.

00:48:45.525 --> 00:48:48.409
제가 깔끔한 코드를 위해서 생략한 것입니다.

00:48:48.409 --> 00:48:54.607
하지만 keras tutorial 을 살펴보시면 아시겠지만
CPU/GPU 전환은 아주 쉽습니다. global flag을 사용하거나

00:48:54.607 --> 00:49:01.635
특정 데이터 타입을 사용하거나 with문을 이용할 수도 있는데
어쩄든 한 줄만 추가하면 될것입니다. 아주 쉽죠

00:49:01.635 --> 00:49:06.149
다만 현재 상황에 따라 선언하는 방식은 상이할 수 있습니다.

00:49:06.149 --> 00:49:14.186
지금 보시는 것은 실제로 여러분들이 앞으로 접하게 될
higher level TensorFlow wrappers들입니다.

00:49:14.186 --> 00:49:21.276
심지어 Google 사람들 조차도 어떤 것이
더 좋은지 잘 모르는 것 같습니다.

00:49:22.230 --> 00:49:26.829
Keras와 TFLearn은 third party libraries 입니다.
Google이 아닌 다른 사람들이 만든 라이브러리들입니다.

00:49:26.829 --> 00:49:32.563
그리고 여기 tf.layers, TF-Slim, tf.contrib.learn
이 있습니다.

00:49:32.563 --> 00:49:39.727
TensorFlow와 함께 제공되는 라이브러리 입니다.
세가지 모두 다 조금씩 다른 방식으로 wrapping되어 있죠

00:49:39.727 --> 00:49:46.291
Google에서 만들었지만 같이 제공하지는 않은 라이브러리도
있습니다. Pretty Tensor라는 것이고 비슷한 맥락입니다.

00:49:46.291 --> 00:49:48.599
하지만 DeepMind에게는 이 모든것들이 만족스럽지 않았나 봅니다.

00:49:48.599 --> 00:49:54.530
몇 주 전에 Sonnet이라는 자체 Tensorflow wapper를 만들었죠

00:49:54.530 --> 00:50:00.715
더이상 나가면 여러분들이 혼란스러운 것 같아 그만하겠습니다.
결론은 여러분들에게 다양한 선택지가 있다는 것입니다.

00:50:00.715 --> 00:50:07.423
그것 들이 상호간에 호환이 잘 안될 수는 있겠지만,
어찌됐든 여러분에게든 여러 옵션이 있는 것이니 좋은 것이죠

00:50:07.423 --> 00:50:09.123
TensorFlow는 pretrained models를 제공합니다.

00:50:09.123 --> 00:50:11.112
여기 주소에 TF-Slim와 Keras에서의 예제가 있습니다.

00:50:11.112 --> 00:50:15.874
학습 시에 pretrained models이 중요하다는 것은 여러분도
잘 하실 것입니다.

00:50:15.874 --> 00:50:21.072
Tensorboard라는 것도 있습니다.
자세히 들어가진 않겠지만

00:50:21.072 --> 00:50:27.747
Tensorflow를 사용하게 되면 Training 하는 동안 Loss와
같은 것들을 plot할 수 있습니다.

00:50:27.747 --> 00:50:32.760
Tensorflow는 분산처리도 지원하기 때문에 서로 다른 머신을
이용해 graph를 쪼개서 실행시킬 수도 있습니다.

00:50:32.760 --> 00:50:37.613
Google 외에 이 기능을 잘 활용하는 사람들을 본 적은 없지만

00:50:37.613 --> 00:50:44.193
만일 여러분이 분산처리를 계획하고 있다면 Tensorflow가
유일한 선택지가 될 것입니다.

00:50:44.193 --> 00:50:51.533
그리고 추가로, Tensorflow는 Montreal에서 만든 Theano
라는 프레임워크의 영감을 받았습니다.

00:50:51.533 --> 00:50:55.933
여기서 더 자세히는 설명히 드리지 않겠지만 여러분이 집에가서
이 슬라이드를 다시 한번 살펴 보시게 되면

00:50:55.933 --> 00:50:59.979
Theano로 작성된 코드가 TensorFlow와 아주 유사하다는 것을
아실 수 있을 것입니다.

00:50:59.979 --> 00:51:03.512
변수를 정의하고 forward pass를 수행하고 그레디언트를
계산합니다.

00:51:03.512 --> 00:51:08.034
그리고 함수 몇개를 컴파일하고
네트워크를 계속 돌리는 것이죠

00:51:08.034 --> 00:51:10.290
이 모든 과정이 TensorFlow와 아주 유사합니다.

00:51:10.290 --> 00:51:16.671
아직 갈 길이 멀군요 PyTorch로 넘어갔다가
질문을 받고 수업을 끝내도록 하겠습니다.

00:51:16.671 --> 00:51:26.397
Facebook에서 나온 PyTorch는 TensorFlow와는 다릅니다.
세 가지 추상화 레벨을 정의해 놓았죠

00:51:26.397 --> 00:51:30.619
PyTorch에는 tensor object가 있습니다.
Numpy array와 유사하죠

00:51:30.619 --> 00:51:36.770
Tensor는 명령형(imperative) 배열이고
GPU에서 수행될 수 있습니다.

00:51:36.770 --> 00:51:44.093
그리고 variable객체는 그래프의 노드라고 할 수 있습니다.
그래프를 구성하고 그레디언트 등을 계산할 수 있습니다.

00:51:44.093 --> 00:51:50.766
그리고 module 객체를 이용해서 Neural network를
구성할 수 있습니다.

00:51:50.766 --> 00:52:01.457
PyTorch와 TensorFlow를 간단히 비교해 보자면 PyTorch
의 Tensor는 Tensorflow의 Numpy array와 같은 역할이죠

00:52:01.457 --> 00:52:08.803
PyTorch의 variable는 Tensorflow의 tensor,
variavle 또는 placeholder와 같습니다.

00:52:08.803 --> 00:52:18.448
PyTorch의 module은  tf.slim이나 tf.layers 혹은 그
밖에 다양한 higher level 프레임워크와 유사합니다.

00:52:18.448 --> 00:52:24.072
PyTorch에 대해 한가지 명심해야 할 점은
PyTorch가 고수준의 추상화를 이미 내장하고 있습니다.

00:52:24.072 --> 00:52:29.780
바로 modules 객체가 그것인데, 때문에 Tensorflow처럼
어떤 모듈을 선택할 지 고민할 필요가 없습니다.

00:52:29.780 --> 00:52:36.642
그런 고민 없이 module 객체만 사용하면 됩니다.

00:52:37.777 --> 00:52:41.944
앞서 말씀 드렸듯 PyTorch의 tensor는
Numpy array와 유사합니다.

00:52:43.660 --> 00:52:47.787
여기 오른쪽 예제를 보시면 PyTorch tensor로 구성한
2-layer 네트워크를 보실 수 있습니다.

00:52:47.787 --> 00:52:53.910
하지만 실제 Numpy array를 사용하지는 않고
PyTorch tensor를 이용합니다.

00:52:53.910 --> 00:53:01.245
이 코드는 Pytorch tensor로 짜여 졌음에도 불구하고 여러분이
첫 과제에서 했던 Numpy 코드와 아주 유사합니다.

00:53:01.245 --> 00:53:07.127
위에서 Random data를 선언하고 forward pass
를 계산하는 연산을 이용합니다.

00:53:07.127 --> 00:53:10.165
그리고 여기에서는 backward pass도 직접 구현했습니다.

00:53:10.165 --> 00:53:15.980
네트워크의 backprop을 구현하는 것을
과제 1에서 여러분들은 다 해 보셨을 것입니다.

00:53:15.980 --> 00:53:22.672
그레디언트를 계산하고 Learning rate를 이용해서
가중치를 직접(manual) 계산했습니다.

00:53:22.672 --> 00:53:27.785
다만 PyTorch tensor와 Numpy의 가장 큰 차이점이 있다면
PyTorch tensor는 GPU에서도 돌아간다는 것입니다.

00:53:27.785 --> 00:53:33.034
이 코드를 GPU에서 실행시키려면 data type만
조금 변경해주면 됩니다.

00:53:33.034 --> 00:53:42.816
FloatTensor 대신에 cuda.FloatTensor을 사용하면
모든 것들이 마법처럼 GPU에서 실행됩니다.

00:53:43.709 --> 00:53:47.637
PyTorch tensor를 쉽게 생각해서
Numpy + GPU 라고 보시면 되겠습니다.

00:53:47.637 --> 00:53:50.818
딥러닝과 직접적으로 맞닿아 있는 것은 아닙니다.

00:53:52.638 --> 00:53:55.278
다음은 variable에 대해 알아보겠습니다.

00:53:55.278 --> 00:54:03.460
variables은 computational graphs를 만들고 이를 통해
그레디언트를 자동으로 계산하는 등의 목적으로 이용합니다.

00:54:03.460 --> 00:54:12.744
X는 variable이고 x.data는 tensor 입니다. x.grad도
variable인데 Loss에 대한 그레디언트를 담고 있습니다.

00:54:14.007 --> 00:54:17.246
따라서 x.grad.data가 실제 tensor이고 이 안에
그레디언트가 담겨 있습니다.

00:54:18.972 --> 00:54:22.387
그리고 PyTorch의 tensors 와 variables는 같은 API를
공유합니다.

00:54:22.387 --> 00:54:28.457
PyTorch tensors로 동작하는 모든 코드는 variables로도
만들 수 있습니다.

00:54:28.457 --> 00:54:34.459
그렇게 되면 imperative한 연산자들이 수행되는 것이 아니라
Computational graph를 만드는 것이 됩니다.

00:54:35.943 --> 00:54:47.461
따라서 여기 보시면 variables를 선언할 때 해당 variables에
대한 그레디언트를 계산할 것인지를 지정해 줄 수 있습니다.

00:54:47.461 --> 00:54:54.073
그리고 forward pass의 경우를 보면 tensor를 사용했을 때와
완전히 같은 코드입니다. 왜냐하면 이 둘은 같은 API이기 떄문이죠

00:54:54.073 --> 00:54:59.683
예측값(y_pred)와 손실(loss)를 계산할 때 이런 식으로
imperative한 방법을 사용할 수 있습니다.

00:54:59.683 --> 00:55:05.251
그리고  loss.backwards 를 호출하게 되면
그레디언트가 알아서 반환이 됩니다.

00:55:05.251 --> 00:55:11.528
그리고 w1.grad.data의 값을 이용해서
가중치 업데이트를 할 수 있습니다.

00:55:11.528 --> 00:55:18.137
그레디언트가 자동으로 계산된다는 것 이외에는
Numpy와 아주 유사합니다.

00:55:18.137 --> 00:55:23.353
그렇다면 Tensorflow와 PyTorch의 차이는 무엇일까요?

00:55:23.353 --> 00:55:27.132
TensorFlow에 경우에는 그래프를 "명시적으로" 구성한 다음에
그 그래프를 돌렸습니다.

00:55:27.132 --> 00:55:32.152
PyTorch의 경우에는 forward pass 할 때 마다 매번
그래프를 다시 구성합니다.

00:55:32.152 --> 00:55:37.058
그때문에 코드가 더 깔끔해 보이기도 합니다.
이에 대해 조금 더 알아보도록 하겠습니다.

00:55:37.058 --> 00:55:40.630
PyTorch에서는 여러분만의 새로운 "자동으로 그레디언트를
계산하는(autograd)" 함수를 정의할 수 있습니다.

00:55:40.630 --> 00:55:42.933
tensors의 형태로 forward와 backward를 정의해주면 됩니다.

00:55:42.933 --> 00:55:48.303
이는 여러분이 과제2에서 수행했던 모듈 레이어와
비슷하게 생겼습니다.

00:55:48.303 --> 00:55:54.433
여러분이 tensor operations을 이용해서 forward/
backward 만 구현하면 그래프에 넣을 수 있습니다.

00:55:54.433 --> 00:56:00.654
여기 예제에서 ReLU를 직접 한번 구현해 봤습니다.
이런 식으로 직접 구현하게 되면

00:56:00.654 --> 00:56:05.214
이 ReLU를 computational graph에 적용할 수 있습니다.

00:56:05.214 --> 00:56:09.097
하지만 대부분의 경우에 이렇게 직접 autograd를
구현할 필요는 없을 것입니다.

00:56:09.097 --> 00:56:14.246
대부분의 경우에 여러분이 필요한 연산들은 이미 구현이
되어있기 때문입니다.

00:56:14.246 --> 00:56:23.349
Tensorflow의 경우에는 Keras나 TF.Learn과 같은 라이브러리가
higher level API 를 제공해 주었습니다.

00:56:23.349 --> 00:56:30.948
PyTorch에서는 그 역할을 "nn package"가 담당합니다.
high level wrappers를 제공해 줍니다.

00:56:31.882 --> 00:56:37.772
다만 TensorFlow에는 종류가 다양했죠 하지만 PyTorch에서는
단 하나만 있는데 아주 잘 동작하고 쓸만합니다.

00:56:37.772 --> 00:56:44.436
Linear/ReLU layer를 model sequence에 추가하는 부분은
Keras와 유사하게 생겼습니다.

00:56:44.436 --> 00:56:49.816
그리고 바로 밑에는 nn package에서 제공하는 손실함수를 정의해 줍니다.
여기에서는 mean squared error loss 이군요

00:56:49.816 --> 00:56:55.214
그리고 매 반복마다 forward pass를 수행하여
prediction 결과를 얻습니다.(첫 번째 라인)

00:56:55.214 --> 00:56:59.054
그리고 손실함수를 실행하여 Loss도 구해줍니다.
(두 번째 라인)

00:56:59.054 --> 00:57:04.021
그리고 loss.backward를 호출하면 매 반복시 마다
그래디언트가 저절로 계산이 됩니다.

00:57:04.021 --> 00:57:07.273
그리고 모델 업데이트를 위해 gradient descent step을
명시적으로 수행시켜 줍니다.

00:57:07.273 --> 00:57:12.749
앞서 말씀드렸듯 PyTorch는 forward pass 할 때 마다 매번
새로운 computational graph를 만들어 주는 것입니다.

00:57:12.749 --> 00:57:17.017
TensorFlow에서 처럼 PyTorch도
optimizer operations를 제공합니다.

00:57:17.017 --> 00:57:23.000
가중치 업데이트 부분을 추상화시켜서
Adam 과 같은 알고리즘을 더 쉽게 쓸 수 있습니다.

00:57:23.000 --> 00:57:28.771
이런 식으로 optimizer 객체를 구성해 놓는 것은
모델에게 파라미터를 optimize하고 싶다고 말해 주는 것입니다.

00:57:28.771 --> 00:57:31.115
learning rate같은 하이퍼 파라미터도 정해줘야 합니다.

00:57:31.115 --> 00:57:39.810
그리고 그레디언트를 계산하고 난 후에 optimizer.step를
호출하게 되면 모델 파라미터가 업데이트 됩니다.

00:57:39.810 --> 00:57:44.714
PyTorch를 사용할 때 여러분들이 아주 흔하게 접하게 될 것은
바로 여러분만의 nn modules을 정의하는 것입니다.

00:57:44.714 --> 00:57:51.801
여러분의 전체 네트워크 모델이 정의되어 있는 class를
nn module class로 작성해야만 합니다.

00:57:51.801 --> 00:58:01.043
module은 일종의 네트워크 레이어라고 보시면 됩니다. 다른
module이 포함될 수도 있고 학습가능한 가중치도 포함이 될 수 있죠

00:58:01.043 --> 00:58:07.051
여기 2-Layer Network 예제를 다시한번 보겠습니다.
nn module class로 작성이 되어있는 예제입니다.

00:58:07.051 --> 00:58:11.672
이 클래스의 생성자를 보면 linear1과 linear2가
선언이 되어 있습니다.

00:58:11.672 --> 00:58:17.257
이 두 개의 module objects를 여러분의 클래스 안에
저장하고 있는 것입니다.

00:58:17.257 --> 00:58:26.466
그리고 forward pass에서는 네트워크 출력을 계산하기 위해 앞서
정의한 모듈도 사용할 수 있고 다양한 autograd도 사용할 수 있습니다.

00:58:26.466 --> 00:58:31.594
forward함수 내부를 한번 들여다보면
입력 x는 variable입니다.

00:58:31.594 --> 00:58:35.817
입력 x가 첫 번째 레이어인 self.linear 1을 통과합니다.

00:58:35.817 --> 00:58:38.129
그리고 autograd op을 사용해서 relu를 계산합니다.

00:58:38.129 --> 00:58:42.233
그리고 그 출력 값이 linear2를 통과해서 다시 값을 출력합니다.

00:58:42.233 --> 00:58:46.633
그리고 이 밑의 나머지 코드는 이전과 동일합니다.

00:58:46.633 --> 00:58:54.676
optimizer를 구성하고 반복문을 돌면서 데이터를 넣어주고
backwards로 그래디언트를 구하고 step으로 업데이트하죠

00:58:54.676 --> 00:59:01.817
이 예제가 PyTorch로 학습을 하는 경우 가장 일반적인 패턴입니다.

00:59:01.817 --> 00:59:11.166
우선 모델을 구성하는 클래스를 정의하고 반복문을 돌면서
이 모델을 업데이트 하는 것입니다.

00:59:11.166 --> 00:59:18.873
PyTorch에는 dataloader가 아주 유용합니다.
dataloader는 여러분을 위해 minibatches를 관리하죠

00:59:18.873 --> 00:59:27.273
학습 도중 Disk에서 minibatches를 가져오는 일련의 작업들을
multi-threading를 통해 알아서 관리해 줍니다.

00:59:27.273 --> 00:59:33.221
여기 처럼 dataloader는 dataset를 wrapping하는
일종의 추상화 객체를 제공해 줍니다.

00:59:33.221 --> 00:59:40.208
실제로 여러분의 데이터를 이용하고자 할 때 데이터를 어떤 방식으로
읽을 것이지를 명시하는 dataset class만 작성해 준다면

00:59:40.208 --> 00:59:44.458
이 class를 dataloader로 wrapping 시켜서 학습을
시킬 수 있을 것입니다.

00:59:44.458 --> 00:59:52.233
여기 보시면 dataloader 객체를 순회하면서 매 반복시 마다
데이터의 minibatch를 적절하게 반환시켜 줍니다.

00:59:52.233 --> 00:59:58.409
그리고 내부적으로 data shuffling이나 multithreaded
dataloading와 같은 것들을 알아서 관리해 주게 됩니다.

00:59:58.409 --> 01:00:04.161
지금 보시는 예제가 여러분이 PyTorch 를 사용할 때
가장 많이 보게 될 코드패턴이라고 보시면 되겠습니다.

01:00:05.583 --> 01:00:07.587
PyTorch는 pretrained models를 제공합니다.

01:00:07.587 --> 01:00:11.521
제가 본것 중에서 PyTorch의 pretrained model이
가장 간드러졌습니다.

01:00:11.521 --> 01:00:14.268
torchvision.models.alexnet(pretained=true)
라고만 쓰면 끝입니다.

01:00:14.268 --> 01:00:18.759
이 코드는 여러분이 이 가중치를 처음 사용하는 경우라면
저절로 pretrained models을 다운로드하게 해줍니다.

01:00:18.759 --> 01:00:24.242
사용하기 아주 쉽습니다.

01:00:24.242 --> 01:00:27.094
PyTorch는 또한 Visdom라는 패키지를 제공합니다.

01:00:27.094 --> 01:00:33.600
Tensorboard와 유사하게 Loss에 대한 통계같은 것들을
시각화해주는 패키지입니다.

01:00:33.600 --> 01:00:38.569
아주 좋은 패키지이긴 하나 제가 직접 사용해볼 기회가 없어서
Visdom이 얼마나 유용한지 말씀드릴 수는 없을 것 같습니다.

01:00:38.569 --> 01:00:45.907
다만 Tensorboard와 Visdom의 차이가 있다면 Tensorboard
의 경우에 computational graph의 시각화를 제공합니다.

01:00:45.907 --> 01:00:50.989
이는 아주 유용한 debugging strategy가 될 수 있죠
하지만 Visdom는 이 기능을 제공하지 않습니다.

01:00:50.989 --> 01:00:54.761
하지만 제가 Visdom을 직접 써보지 않아서 활요에 대해
심도깊게 말씀드리기 어려울 것 같습니다.

01:00:56.350 --> 01:01:05.491
어쨋든, PyTorch는 제가 지난 수년간 사용했던 Torch 라는
프레임워크가 새롭게 진화된 프레임워크입니다.

01:01:05.491 --> 01:01:13.280
깊게 말씀드리진 않겠지만 PyTorch가 기존의 Lua Torch보다
훨씬 더 좋은 것은 사실입니다만

01:01:13.280 --> 01:01:18.100
사실 이 둘 모두 동일한 C code Backend를 가지고 있습니다.
tensors를 계산하거나 GPU 연산을 하는 등에 있어서 말이죠

01:01:18.100 --> 01:01:23.369
여기 Torch 예제를 보시면 일부는 PyTorch와 유사하고
또 일부는 조금 다른 부분도 있습니다.

01:01:23.369 --> 01:01:25.957
집에 가서 한번 살펴보시기 바랍니다.

01:01:25.957 --> 01:01:33.011
Torch와 PyTorch의 주된 차이점을 한번 비교해 보자면
Torch는 Python이 아닌 Lua로 작성되어 있습니다.

01:01:33.011 --> 01:01:37.748
Lua를 배우는 것은 일부 사람들에게 있어서는 시간낭비
일 수도 있을 것입니다.

01:01:37.748 --> 01:01:40.009
Torch는 autograd도 제공하지 않습니다.

01:01:40.009 --> 01:01:44.324
하지만 Torch가 더 오래되었기 때문에 안정성이 조금 더 보장되고
버그도 적습니다. 그리고 Torch의 예제코드가 더 많죠

01:01:45.230 --> 01:01:47.214
이 둘의 연산 속도도 거의 비슷합니다.

01:01:47.214 --> 01:01:54.531
반면 PyTorch는 Python으로 작성할 수 있으며 autograd도
지원합니다. 복잡한 모델을 더 쉽게 다룰 수 있겠지요

01:01:54.531 --> 01:01:59.670
Lua Torch는 대부분의 경우 backporp 코드를 직접 작성해야
합니다. 아주 귀찮은 일입니다.

01:01:59.670 --> 01:02:06.051
하지만 PyTorch가 더 최신에 나왔기 때문에 예제 코드가 더 적습니다.
따라서 PyTorch를 사용하는 것이 좀 더 모험적인 일이 될 수 있습니다.

01:02:06.051 --> 01:02:17.765
하지만 저같은 경우에는 굳이 PyTorch를 두고 Torch를 써야만 하는
이유를 못느끼기 때문에 현재는 거의  PyTorch만 사용하고 있습니다.

01:02:18.606 --> 01:02:22.531
앞서 static graphs VS dynamic graphs 에 대해서
말씀드린 적이 있었죠

01:02:22.531 --> 01:02:26.291
이는 PyTorch와 TensorFlow의 주된 차이점 중 하나입니다.

01:02:26.291 --> 01:02:38.145
TF는 두 단계로 나뉩니다. 첫째는 그래프를 구성하는 단계입니다.
그리고 두번째는 이 그래프를 반복적으로 돌리는 단계입니다.

01:02:38.145 --> 01:02:42.403
이를 static computational graph라고 합니다.
그래프가 단 하나만 고정적으로 존재하기 때문이죠

01:02:42.403 --> 01:02:48.771
PyTorch는 완전히 다른 방식입니다. 매번 forward pass할 때
마다 새로운 그래프를 구성합니다.

01:02:48.771 --> 01:02:52.259
이를 dynamic computational graph라고 합니다.

01:02:52.259 --> 01:02:57.053
이런 단순한 feed forward neural network의 경우에는
별로 큰 차이점이 없어 보일 수도 있습니다.

01:02:57.053 --> 01:03:00.225
코드도 비슷하게 생겼고
동작도 아주 비슷하죠

01:03:00.225 --> 01:03:07.102
하지만 static과 dynamic간의 서로 다른 특징과 trade-off에
대해서 조금 더 말씀드리고자 합니다.

01:03:07.102 --> 01:03:15.286
static graphs의 관점에서 보면 사실 그래프를 한번 구성해 놓으면
학습시에 똑같은 그래프를 아주 많이 재사용하게 됩니다.

01:03:15.286 --> 01:03:19.571
static graphs에서는 그 그래프를 최적화시킬
기회가 주어질 수 있는 것입니다.

01:03:19.571 --> 01:03:26.809
일부 연산들을 합쳐버리고 재배열시키는 등으로 가장 효율적으로
연산을 하도록 최적화 시킬 수 있는 것이죠. 아주 효율적인 방법입니다.

01:03:26.809 --> 01:03:33.039
그래프 하나만 하지고 아주 여러번 사용할 것이기 때문에
처음의 최적화작업 자체가 조금 오래 걸릴순 있어도

01:03:33.039 --> 01:03:37.230
최적화된 그래프를 여러번 사용한다는 것을 고려해보면
최적화에 소요되는 시간은 아무것도 아닐 수 있습니다.

01:03:37.230 --> 01:03:44.085
조금 더 구체적인 예를 들어보자면 여기 Conv와 ReLU가
반복되는 모델이 있다고 해봅시다.

01:03:44.085 --> 01:03:54.530
이 그래프를 최적화 한다고 했을때 Conv와 ReLU를 합쳐버리는 것을
생각해 볼 수 있을 것입니다.

01:03:54.530 --> 01:04:03.445
어짜피 합쳐도 같은 연산을 하는 것이고 대신
코드는 더 효율적으로 실행될 수 있을 것입니다.

01:04:03.445 --> 01:04:10.419
저도 TensorFlow graph optimization가 실제로
어떤 방식으로 동작하는지 확실하게 알지는 못하지만

01:04:10.419 --> 01:04:20.131
하지만 확실한 것은 static graphs에서는
그래프를 최적화시킬 수 있는 여지가 주어진다는 것입니다.

01:04:20.131 --> 01:04:24.298
dynamic graphs 에서는 그래프 최적화를 다루기 어려울 것입니다.

01:04:25.504 --> 01:04:28.931
static vs dynamic의 또 한가지 차이점은
serialization에 관한 것입니다.

01:04:28.931 --> 01:04:34.026
static graph 를 사용하여 그래프를 구성한다고 해봅시다

01:04:34.026 --> 01:04:39.571
그래프를 한번 구성해 놓으면 메모리 내에
그 네트워크 구조를 가지고 있는 것입니다.

01:04:39.571 --> 01:04:42.428
그렇게 되면 그 자체를 Disk에 저장할 수 있을 것입니다.

01:04:42.428 --> 01:04:45.996
이렇게 되면 전체 네트워크 구조를 파일 형태로 저장할 수 있는 것입니다.

01:04:45.996 --> 01:04:55.450
그러고 나면, 원본 코드 없이도 그래프를 다시 불러올 수 있습니다.
이는 아주 좋은 특성입니다.

01:04:55.450 --> 01:05:00.424
가령 여러분은 네트워크를 Python으로 학습시키고 싶을 것입니다.
Python이 쉽기 때문이죠

01:05:00.424 --> 01:05:07.759
그리고 이를 serialize하면, C++환경에서 아주 쉽게 동작시킬 수
있습니다. 그래프를 구성하는데 기존의 코드는 필요하지 않습니다.

01:05:07.759 --> 01:05:10.909
이는 static graphs의 아주 좋은 이점이라고 할 수 있습니다.

01:05:10.909 --> 01:05:15.793
반면에 dynamic graph의 경우에는 "그래프 구성" 과 "그래프 실행"
하는 과정이 얽혀 있기 때문에(interleaving)

01:05:15.793 --> 01:05:22.012
모델을 재사용하기 위해서는 항상 원본 코드가 필요합니다.

01:05:22.012 --> 01:05:29.163
하지만 dynamic graphs의 장점은 대다수의 경우에
코드가 훨씬 더 깔끔하고 작성하기 더 쉽습니다.

01:05:29.163 --> 01:05:38.624
가령 조건부 연산(if 문)을 해야하는 상황을 가정해 봅시다.
변수 z의 값에 따라서 y값이 달라지는 경우입니다.

01:05:39.723 --> 01:05:45.070
Z가 양수면 w1*x 연산을, 음수면 w2*x 연산을 수행해야 하죠

01:05:45.070 --> 01:05:47.981
조건부 연산을 사용해서 이 두 가지 연산을
오가야 하는 상황입니다.

01:05:47.981 --> 01:05:52.011
PyTorch에서는 dynamic graphs를 사용하므로
이 문제를 다루기 아주 수월합니다.

01:05:52.011 --> 01:06:00.795
Numpy와 아주 유사합니다. 이 문제를 다루데는
일반적인 Python if문을 사용하면 그만이죠

01:06:00.795 --> 01:06:05.563
dynamic graphs의 경우에는 매번 새로운 그래프를 구성하기 때문에

01:06:05.563 --> 01:06:10.864
매번 이 두 가지 선택지 중에 현재 forward pass에
적절한 하나를 선택해서 새로운 그래프를 만들어주면 그만입니다.

01:06:10.864 --> 01:06:14.337
새롭게 구성된 그래프로 backporb에도 아무 문제가 없겠죠

01:06:14.337 --> 01:06:15.941
코드를 아주 깔끔하게 짤 수 있습니다.

01:06:15.941 --> 01:06:23.201
TensorFlow에서는 조건부 연산을 넣기 조금 더 복잡합니다.
왜냐하면 우선 그래프를 하나 만들어 놔야 합니다.

01:06:23.201 --> 01:06:28.400
그래프 내에 조건부 연산을 명시적으로 정의하는
Control flow operator를 추가해야만 합니다.

01:06:28.400 --> 01:06:36.818
Tensorflow의 예제에서는 tf.cond라는 것을 보실 수 있습니다.
Tensorflow버전의 if문 이라고 보시면 됩니다.

01:06:36.818 --> 01:06:40.741
Python의 if문을 쓰는 것 대신에 이런 식으로 그래프 내에
control flow자체를 넣어줘야 하는 것입니다.

01:06:40.741 --> 01:06:48.729
Tensorflow에서는 그래프가 단 한번만 만들어지기 때문에
가능한 모든 control flow를 미리 고려해서

01:06:48.729 --> 01:06:52.523
그래프 내에 한번에 넣어줘야만 합니다.
그래프를 실제로 실행시키기 전에 말이죠

01:06:52.523 --> 01:07:03.360
Control flow를 위해서는 간단한 python 문법으론 불가능하고
반드시 특수한 Tensor Flow 연산자가 필요하다는 것입니다.

01:07:03.360 --> 01:07:05.527
지금 예제의 경우에는 tf.cond이 되겠습니다.

01:07:06.713 --> 01:07:10.763
조건부 연산과 유사한 상황이
반복연산(loop) 에서도 발생합니다.

01:07:10.763 --> 01:07:19.839
가령 어떤 재귀적인 연산을 한다고 해봅시다.
Y_t = (Y_t-1 + X) * W 같은 경우죠

01:07:19.839 --> 01:07:26.436
이런 연산을 수행할 때 우리의 데이터 sequence는
다양한 사이즈일 수 있습니다.

01:07:26.436 --> 01:07:33.371
데이터의 sequence 길이가 얼마인지 신경쓰지 않고
재귀연산을 할 수 있다면 좋겠죠

01:07:33.371 --> 01:07:39.489
PyTorch의 경우에 엄청나게 간단합니다.
Python에서 제공하는 기본 for loop를 이용하면 끝입니다.

01:07:39.489 --> 01:07:47.095
적절한 Loop만 돌면 됩니다. 이를 통해 데이터 사이즈에 맞는
적절한 그래프를 손쉽게 만들 수 있습니다.

01:07:47.095 --> 01:07:51.694
그리고 이는 backprob에도 지장이 없습니다.

01:07:51.694 --> 01:07:55.782
하지만 TensorFlow에서는 이 과정이 아주 지저분합니다.

01:07:55.782 --> 01:08:06.364
Tensorflow에서는 그래프를 앞에서 미리 만들어줘야 하기 때문에
그래프에 명시적으로 loop를 넣어줘야만 합니다.

01:08:06.364 --> 01:08:13.517
여러분이 함수형 프로그래밍을 잊지 않고있길 바랍니다. 왜냐하면
TensorFlow에서 looping을 구현하려면 반드시 필요하기 때문이죠

01:08:13.517 --> 01:08:23.024
이 경우에 특정 재귀적인 관계를 정의하기 위해서는
tf.foldl연산을 사용해서 구현해볼 수 있습니다.

01:08:24.100 --> 01:08:28.734
TensorFlow에 대해 기본적으로 말씀드리고 싶었던 것은
TensorFlow를 사용하게 되면 여러분들은 반드시

01:08:28.734 --> 01:08:33.212
computational graphs의 "전체 흐름" 을 여러분들이
전부 다 구성해 놔야 한다는 것입니다.

01:08:33.212 --> 01:08:37.215
그래프에 필요한 모든 control flow연산 그리고 모든 데이터 구조
등을 전부다 구현해 놔야 합니다.

01:08:37.215 --> 01:08:44.216
따라서 Tensorflow에서는 Python의 명령어들을 같이 활용할
여지가 별로 없습니다.

01:08:44.216 --> 01:08:52.804
TF에서 필요한 control flow 연산들을 전부 다시 배워야만
합니다. TensorFlow에서 그것을 사용해야 한다면 말이죠.

01:08:52.804 --> 01:08:58.238
적어도 저의 경우에는 Tenforflow를 사용할 때 상당히 혼란스러웠습니다.

01:08:58.238 --> 01:09:06.722
반면에 PyTorch의 dynamic graphs를 사용하는 경우라면 다양한
Python 명령어들을 이용할 수 있습니다.

01:09:07.737 --> 01:09:21.579
사실 TensorFlow Fold라는  TF 라이브러리가 있습니다.
TensorFlow에서 dynamic graphs를 작성하게 해줍니다.

01:09:22.416 --> 01:09:32.277
TensorFlow Fold가 dynamic graph를 만들어주는 것 처럼
보이지만 사실 static graph로 만든 트릭입니다.

01:09:32.277 --> 01:09:37.357
TF Fold는 이번주에 ICLR in France에서 발표된
아주 최신의 연구(논문)입니다.

01:09:37.358 --> 01:09:41.694
그래서 아직 TF-Fold를 써보지는 못했습니다.

01:09:41.694 --> 01:09:46.455
하지만 제 첫인상으로는 TF-Fold를 통해 TensorFlow로
dynamic graphs 역할을 더해줄 순 있으나

01:09:46.455 --> 01:09:51.952
아직까지는 PyTorch같은 네이티브 dynamic graphs
에 비하면 많이 어색해 보였습니다.

01:09:51.952 --> 01:09:57.257
그렇다면 어떤 상황에서 dynamic graph의
사용을 고려해 볼만 한 것일까요?

01:09:57.257 --> 01:10:00.257
그중 하나는 바로 recurrent networks 의 경우입니다.

01:10:01.177 --> 01:10:07.612
image captioning의 경우가 그렇습니다. 이는 다양한 길이의
sequences를 다루기 위해 RNN을 이용합니다.

01:10:07.612 --> 01:10:13.337
이 예시의 경우에는 우리가 만들어내고자
하는 caption이 바로 sequence입니다.

01:10:13.337 --> 01:10:15.636
이 caption이라는 sequence는 입력 데이터에
따라서 다양하게 변할 수 있습니다.

01:10:15.636 --> 01:10:21.694
이 문제의 경우에는 dynamism을 가지고 있습니다.
sentence의 크기에 따라서 -

01:10:21.694 --> 01:10:25.716
computational graph가 더 커질 수도,
작아질 수도 있을 것입니다.

01:10:25.716 --> 01:10:29.920
Image caption은 dynamic graphs를 이용하는
가장 일반적인 예시 중 하나입니다.

01:10:29.920 --> 01:10:36.377
지난 학기에 CS224N를 수강하신 분들이라면
recursive networks에 대해서 들어본 적이 있으실 것입니다.

01:10:36.377 --> 01:10:47.337
가령 자연어 처리 분야에서 문장을 파싱하는 문제에서 트리를
파싱하기 위해 recursive한 네트워크가 필요할 수 있습니다.

01:10:47.337 --> 01:10:56.856
이런 문제의 경우 layer의 "sequence 구조"를 이용하기 보다는
"graph 나 tree 구조" 를 이용합니다.

01:10:56.856 --> 01:10:58.732
데이터에 따라 다양한 graph 또는 tree 구조를
가질 수 있습니다.

01:10:58.732 --> 01:11:05.714
따라서 그래프를 구성할 때 데이터의 구조를 반영하도록 구성하면
다양한 데이터에도 유동적인 네트워크를 만들 수 있을 것입니다.

01:11:05.714 --> 01:11:10.316
이런 네트워크를 구성하려면 Tensorflow에서는
정말 복잡하고 까다로울 수 있습니다.

01:11:10.316 --> 01:11:14.887
하지만 PyTorch를 사용하게 되면 기존의 Python
control flow 만 사용하더라도 충분합니다.

01:11:16.574 --> 01:11:23.678
VQS를 다루는 Neuromodule라는 연구가 있습니다.
아주 멋진 아이디어입니다.

01:11:23.678 --> 01:11:31.737
"이미지"와 "질문"을 던지면 적절한 답을 하는 것이죠. 이 이미지에는
고양이와 강아지가 있군요

01:11:31.737 --> 01:11:43.594
"고양이의 색은?" 과 같은 질문을 던지면 이 문제를 풀기에 적합한 네트워크
를 구성합니다. 여기에선 "색" 과 "고양이" 를 찾게 되겠죠

01:11:43.594 --> 01:11:49.838
질문이 주어지면 그 질문에 답하기 위한 적절한 네트워크
(custom)를 구성하는 것입니다.

01:11:49.838 --> 01:11:55.094
"고양이가 강아지보다 많은지?" 와 같은 질문을 한다면 어떻까요?

01:11:55.094 --> 01:12:03.076
이 경우에도 이전과 같이 "고양이"와 "강아지" 를 찾는 네트워크가
들어갈 테지만 조금은 구성이 달라질 것입니다.

01:12:03.076 --> 01:12:07.716
여기에서 dynamism이 적용될 것입니다. 문제가 달라지면
computation graph도 다르게 구성되겠죠

01:12:07.716 --> 01:12:12.574
아직 연구가 진행 중인 분야이며
아직 까지는 main stream은 아닙니다.

01:12:12.574 --> 01:12:19.214
가장 중요하게 말씀드리고 싶은 것은 dynamic graph를
이용한다면 정말 많은 것들을 할 수 있다는 것입니다.

01:12:19.214 --> 01:12:23.471
아직은 Dynamic 그래프를 다루는 것이 쉽지 않기 때문에
관련 연구가 그렇게 많지는 않습니다.

01:12:23.471 --> 01:12:30.596
Dynamic computrational graphs를 사용한다면 아주 다양한
대단하고 창의적인 것들을 만들어 낼 수 있을 것이라 생각합니다.

01:12:30.596 --> 01:12:34.078
여러분들이 좋은 아이디어를 만들어 내신다면
제가 내년 이 강의해서 소개해 드리도록 하겠습니다.

01:12:34.078 --> 01:12:39.854
자 이제 Caffe에 대해서 간단하게 말씀드려보겠습니다.
Berkeley에서 처음 만든 프레임워크입니다.

01:12:39.854 --> 01:12:48.815
 Caffe는 다른 딥러닝프레임워크랑은 다소 다릅니다.
많은 경우에 코드를 작성하지 않아도 네트워크를 학습시킬 수 있죠

01:12:48.815 --> 01:12:53.214
기존에 빌드된 바이너리를 호출하고 Configuration
파일만 조금 손보면

01:12:53.214 --> 01:12:56.697
굳이 코드를 작성하지 않아도 데이터를
학습시킬 수 있습니다.

01:12:56.697 --> 01:13:03.054
우선 데이터를 HDF5 또는 LMDB포맷으로 변환시켜 줍니다.

01:13:03.054 --> 01:13:08.638
이는 Caffe에서 지원하는 스크립트를 이용하면 됩니다. 폴더안에
저장된 이미지를 적절한 포맷으로 변환시켜줍니다.

01:13:08.638 --> 01:13:19.934
Caffe에서는 그래프를 구성하려면 코드를 작성하는 대신에
prototxt라는 텍스트파일을 만들어야 합니다.

01:13:19.934 --> 01:13:30.875
여기 구조를 보시면 입력을 HDF5 파일로 받는 부분도 있고
내적을 하는 부분도 있습니다. 이런 형식으로 그래프를 구성합니다.

01:13:30.875 --> 01:13:35.956
이런 방식은 네트워크의 규모가 커지면
상당히 보기 안좋다는 단점이 있습니다.

01:13:35.956 --> 01:13:44.253
가령 152레이어의 ResNet 모델을 Caffe로 훈련시키려 하면
prototxt 파일은 거의 7000줄에 이르게 됩니다.

01:13:44.253 --> 01:13:51.817
그래서 사람들은 이것을 손으로 직접 작성하기보다는
prototxt 파일을 생성하는 Python 스크립트를 작성하곤 합니다.

01:13:51.817 --> 01:13:53.275
[웃음]

01:13:53.275 --> 01:13:58.974
Caffe에서는 그래프를 하나하나 전부 다 기술해 줘야 합니다.
그닥 좋은 방법은 아닌 것 같아 보이지만 말이죠

01:13:58.974 --> 01:14:07.497
그리고 Caffe는 optimizer나 solver를 정의하기 위한
또 다른 prototxt 파일을 작성해야 합니다.

01:14:07.497 --> 01:14:11.036
그 곳에 learning rate와 optimiation algorithm
같은 것들을 정의해 줍니다.

01:14:11.036 --> 01:14:17.278
이런 식의 작업을 모두 마치고  Caffe의 바이너리만
실행시키면 학습이 자동으로 수행됩니다.

01:14:17.278 --> 01:14:21.294
Caffe에도 아주 다양한 pretrained model을 제공하는
 model zoo가있습니다. 아주 유용하죠

01:14:21.294 --> 01:14:25.438
그리고 Caffe는 Python 인터페이스를 지원하긴 하지만
문서화가 잘 되어있는 편은 아닙니다.

01:14:25.438 --> 01:14:31.455
python 인터페이스와 관련된 소스코드도 있긴 하지만
상당히 불편합니다. 그렇긴 해도 동작은 합니다.

01:14:31.455 --> 01:14:40.174
Caffe의 경우에는 feed forward 모델에 적합한 편입니다.
그리고 production의 측면에 적합하다고 볼 수 있습니다.

01:14:40.174 --> 01:14:42.796
왜냐하면 Python에 의존적이지 않기 때문이죠

01:14:42.796 --> 01:14:47.358
하지만 오늘날 Caffe는 research의 목적으로는
잘 사용하지 않습니다.

01:14:47.358 --> 01:14:51.417
하지만 Caffe는 industry에서 production
개발을 위해서 여전히 많이 사용합니다.

01:14:51.417 --> 01:14:54.410
한 두개의 슬라이드로 Caffe2를 간단히 설명드리겠습니다.

01:14:54.410 --> 01:14:58.596
Caffe 2를 Caffe의 다음 버전입니다.
 Facebook에서 만들었죠

01:14:58.596 --> 01:15:02.432
지난 주에 배포된 아주 따끈한 신상이죠
(2017년 4월 27일 기준)

01:15:02.432 --> 01:15:04.436
[웃음]

01:15:04.436 --> 01:15:09.314
Caffe2를  유심히 살펴볼 새가 없었습니다.

01:15:09.314 --> 01:15:12.318
Caffe2도 TensorFlow처럼 Static graph를 사용합니다.

01:15:12.318 --> 01:15:17.817
Caffe처럼 코어는 C++로 작성되어있고
Python 인터페이스도 제공합니다.

01:15:17.817 --> 01:15:21.518
Caffe1과 다른점이 있다면 더이상 prototxt 파일을
만들기 위해 Python 스크립트를 작성할 필요가 없다는 것입니다.

01:15:21.518 --> 01:15:29.657
Tensorflow 스럽게 Python으로 그래프를 작성할 수 있습니다.

01:15:29.657 --> 01:15:34.596
그리고 이 그래프를 prototxt 파일로 변환시켜 줄 수 있습니다.

01:15:34.596 --> 01:15:38.676
모델을 한번 훈련시켜 놓게 되면 앞서 Static 그래프의
이점에 대해서 말씀드렸듯이

01:15:38.676 --> 01:15:43.534
기존의 소스코드 없이도 학습시킨 모델을 쓸 수 있는 것입니다.

01:15:43.534 --> 01:15:49.417
한 가지 흥미로운 점은 Google의 경우에는 주력 딥러닝
프레임워크가 TensorFlow 하나 뿐입니다. 하지만 -

01:15:49.417 --> 01:15:53.761
Facebook의 경우에는 두 개죠
PyTorch와 Caffe2 입니다.

01:15:54.596 --> 01:15:57.252
이 차이는 서로 다른 철학에서 비롯됩니다.

01:15:57.252 --> 01:16:02.847
Google의 경우에는 딥러닝이 필요한 모든 곳에서
동작하는 프레임워크를 만들고싶어 합니다.

01:16:02.847 --> 01:16:07.852
그래서 통합된 프레임워크 하나를 만들고
그곳에 올인하는 것이죠

01:16:07.852 --> 01:16:13.772
TF하나로 distributed systems, production,
deployment, mobile, research를 다 커버하도록 말이죠

01:16:13.772 --> 01:16:15.706
이 모든 것을 다루는데 프레임워크 하나면 충분합니다.

01:16:15.706 --> 01:16:18.151
하지만 Facebook의 생각은 조금 다릅니다.

01:16:18.151 --> 01:16:26.071
PyTorch는 연구(research)에 특화되어 있습니다.
연구 사이클을 상당히 단축하도록 만들어졌습니다.

01:16:26.071 --> 01:16:32.951
연구에는 PyTorch가 쉽지만 제품(production)개발을
위한 지원이 그닥 많지 않습니다.

01:16:32.951 --> 01:16:37.710
대신 Caffe2가 그 역할을 담당하는 것입니다.

01:16:39.567 --> 01:16:47.350
그렇다면 어떤 프레임워크를 어떻게 사용해야할지에 대한
제 주관적인 의견을 말씀드리자면

01:16:47.350 --> 01:16:53.510
Tensorflow 같은 경우에는 여러분이 어떤 프로젝트를
하던 상당히 괜찮은 선택입니다.

01:16:53.510 --> 01:16:58.849
Tensorflow는어떤 환경에서든 잘 동작하는 만능
프레임워크이기 때문입니다.

01:16:58.849 --> 01:17:05.207
하지만 여러 higher level wrapper를 섞어 써야 하거나
Dynamic graph가 필요한 경우 곤란해 질 수도 있습니다.

01:17:05.207 --> 01:17:13.190
Tensorflow의 Dynamic  그래프 코드가 조금 지저분하기 떄문이죠
물론 제 개인적인 의견이고, 사실 큰 문제도 아닙니다.

01:17:13.190 --> 01:17:15.809
저는 개인적으로 PyTorch가 research에는
아주 유용하다고 생각합니다.

01:17:15.809 --> 01:17:21.233
연구목적으로만 사용한다면 PyTorch가
최고의 선택이 될 수 있습니다.

01:17:21.233 --> 01:17:25.649
하지만 나온지 얼마 안되서 커뮤니티 규모가 작고
찾을만한 코드도 적습니다. 모험을 해야할 수도 있습니다.

01:17:25.649 --> 01:17:29.969
이미 사람들이 잘 다져놓은 길을 가고 싶다면
Tensorflow가 더 좋은 선택일 수 있습니다.

01:17:29.969 --> 01:17:34.710
만약 여러분이 제품을 배포하려는 목적이라면
Caffe, Caffe2 또는 Tensorflow를 선택해야 합니다.

01:17:34.710 --> 01:17:41.270
또한 특히 모바일 디바이스라면 Tensorflow와 Caffe2가
지원하는 것으로 알고 있습니다.

01:17:41.270 --> 01:17:47.393
불행하게도 무조건 이 프레임워크가 최고다 라는 것은 없습니다.
선택은 여러분의 문제의 성격에 달려있는 것입니다.

01:17:47.393 --> 01:17:52.045
앞서 말씀드린 내용들을 기준들은
제가 일반적으로 조언해 드리는 내용입니다.

01:17:53.169 --> 01:17:55.691
다음 강의에서는 다양한 CNN 아키텍쳐에 대해서
배워보도록 하겠습니다.